package
0.0.0-20241213082245-5ebc85362f6c
Repository: https://github.com/sebnyberg/leetcode.git
Documentation: pkg.go.dev

# README

This post will go through optimization steps toward a solution that is near-optimal for this problem, and optimal for the followup problem: 188.

First solution

Form a matrix profits[k][i] where the value of a cell at profits[k][i] is the maximum profit given by k+1 possible trades at the time i.

ntrades := 2
ndays := len(prices)
profits := make([][]int, ntrades)
for i := range profits {
  profits[i] = make([]int, ndays)
}

Filling the first row is easy - the maximum profit at day i is the difference between the current price and the minimum price before day i:

minVal := prices[0]
for i := 1; i < ndays; i++ {
  profits[0][i] = max(profits[0][i-1], prices[i]-minVal)
  minVal = min(minVal, prices[i])
}

Given prices=[3,3,5,0,0,3,1,4], profits[k][i] becomes:

  i 0 1 2 3 4 5 6 7
k \ - - - - - - - - 
0 | 0 0 2 2 2 3 3 4

For k > 1, the profit from making a trade depends on the maximum profit of previous trades. The easiest way to take this into account is to adjust the price on a given day by the maximum profit made from a trade made before that day.

For example, the adjusted price for the second trade would become:

[ 3  3  5  0  0  3  1  4 ] prices
[ 0  0  2  2  2  3  3  4 ] profits[0]
[ 0  3  5 -2 -2  0 -2  1 ] adjusted = prices[i] - profits[i-1]

It is now clear that the value of making a trade on a given day for k > 1 is given by the recurrence relation:

  • adjusted[k][i] = prices[i] - profits[k-1][j-1]
  • profits[k][i] = max(profits[k][i-1], prices[i]-min(adjusted[k][:i]...)

Which gives the first solution:

for k := 1; k < ntrades; k++ {
  for i := 1; i < ndays; i++ {
    minPriceBeforeToday := adjustedPrices[0]
    for j := 1; j < i; j++ {
      minPriceBeforeToday = min(minPriceBeforeToday, adjustedPrices[j])
    }
    profits[k][i] = max(profits[k][i-1], prices[i]-minPriceBeforeToday)
    adjustedPrices[i] = prices[i] - profits[k-1][i-1]
  }
}

Optimization 1: Keeping track of the minimum value

Instead of finding the minimum value of adjusted[k][:i] for each index i, the minimum cost of buying a stock before the day i can be kept track of, i.e.:

minPrice = [ 3  3  3 -2 -2 -2 -2 -2 ]

Which gives:

minPrices := make([]int, ndays)
minPrices[0] = prices[0]
for k := 1; k < ntrades; k++ {
  for i := 1; i < ndays; i++ {
    minPrices[i] = min(minPrices[i-1], prices[i]-profits[k-1][i-1])
    profits[k][i] = max(profits[k][i-1], prices[i]-minPrices[i])
  }
}

Optimization 2: using a single minimum value

Instead of keeping a list of minimum values, keep only the minimum value for any day before the current one.

var minPrice int
for k := 1; k < ntrades; k++ {
  minPrice = prices[0]
  for i := 1; i < ndays; i++ {
    // Deducting the minimum price by the profits the day / trade before,
    // we artificially add value to to the current trade
    minPrice = min(minPrice, prices[i]-profits[k-1][i-1])
    profits[k][i] = max(profits[k][i-1], prices[i]-minPrice)
  }
}

Optimization 3: using a single list of profits

By updating the minimum price and current place, it's possible to use a single row of profits rather than a matrix:

ntrades := 2
ndays := len(prices)
profits := make([]int, ndays)

var minPrice int
for k := 0; k < ntrades; k++ {
  minPrice = prices[0]
  for i := 1; i < ndays; i++ {
    profits[i], minPrice = max(profits[i-1], prices[i]-minPrice),
      min(minPrice, prices[i]-profits[i])
  }
}

return profits[ndays-1]

Optimization 4: skip days which cannot improve the profits

Finally, there is no point in updating the list of profits for days for which it is not possible to gain from a previous trade:

ntrades := 2
ndays := len(prices)
profits := make([]int, ndays)

var minPrice int
for k := 0; k < min(ntrades, ndays/2); k++ {
  minPrice = prices[0]
  for i := 1; i <= k*2; i++ {
    minPrice = min(minPrice, prices[i]-profits[i])
  }
  for i := 1 + k*2; i < ndays; i++ {
    profits[i], minPrice = max(profits[i-1], prices[i]-minPrice),
      min(minPrice, prices[i]-profits[i])
  }
}

return profits[ndays-1]