Problem overview
The function max_profit receives a vector of daily prices and a limit k on the number of transactions. Each transaction is a buy followed by a later sell, and you may hold at most one purchase at a time. The function returns the largest total profit.
For prices = [3, 2, 6, 5, 0, 3] and k = 2 the answer is 7: buy at 2 and sell at 6 for 4, then buy at 0 and sell at 3 for 3. For [2, 4, 1] the answer is 2, from a single transaction.
Approach: replace the buy-day scan with a running maximum
The table is the same as in the slower solution. dp[t][i] is the best profit using at most t transactions in the first i days. The slower solution found the sale on day i by trying every earlier buy day j, which costs O(n) per cell and O(k * n²) overall.
Look at what that scan computes. For a sale on day i after a buy on day j, the total is
dp[t - 1][j - 1] + prices[i - 1] - prices[j - 1]
The price of day i is the same for every j, so only the part that depends on j matters: dp[t - 1][j - 1] - prices[j - 1]. The best sale on day i is that part's maximum over all earlier buy days, plus the current price. And that maximum changes by only one candidate per day, so it can be kept in a variable and updated as i moves forward. This running best is max_diff, and it turns the inner loop into constant work.
Row t - 1 is complete before row t starts, so its values can be read freely. The buy candidate for day i is dp[t - 1][i] - prices[i - 1]: the best profit from t - 1 transactions over the first i days, minus today's price. Using the first i days rather than the first i - 1 is fine: it amounts to selling and buying again on the same day, which changes nothing, since the profit counts the same as holding on.
Step by step
- Store the length in
nand rebindkas ausize. - Create the table of
k + 1rows andn + 1columns of zeros, mutable, asi32. - Loop over
tfrom 1 tok. - Declare
max_diffinside that loop withlet mut, starting ati32::MIN. No day has been seen yet, so the running best must lose to any real candidate, and candidates can be negative. Eachtstarts its own running best. - Loop over
ifrom 1 ton. - Choose the best of three options for
dp[t][i]: no sale on dayi(dp[t][i - 1]), fewer transactions (dp[t - 1][i]), or selling today the stock bought on the best earlier day (max_diff + prices[i - 1]). Chain twomaxcalls to compare three values. - Update
max_diffwith today's buy candidate,dp[t - 1][i] - prices[i - 1], keeping the larger value. This comes after computingdp[t][i], since today's candidate is meant for sales on later days. - Return
dp[k][n].
The solution in Rust
impl Solution {
fn max_profit(prices: Vec<i32>, k: i32) -> i32 {
let n = prices.len();
let k = k as usize;
let mut dp = vec![vec![0i32; n + 1]; k + 1];
for t in 1..=k {
let mut max_diff = i32::MIN;
for i in 1..=n {
dp[t][i] = dp[t][i - 1].max(dp[t - 1][i]).max(max_diff + prices[i - 1]);
max_diff = max_diff.max(dp[t - 1][i] - prices[i - 1]);
}
}
dp[k][n]
}
}
The Rust details worth knowing:
- Chained method calls.
a.max(b).max(c)is the maximum of three values: the first call yields ani32, and the second compares it withc. i32::MINas a starting value. It is the smallesti32, -2147483648, so any real candidate replaces it. It is risky to add to, though:max_diff + prices[i - 1]on day 1 evaluatesi32::MIN + price. With a price of 0 or more the sum only moves up, so nothing wraps. A negative price would push it below the minimum, and in an optimized build that wraps silently into a huge positive number.- Variable scope.
max_diffis declared inside thetloop, so it exists only for the current row and starts fresh for the next one. - Mutation with
mut. Bothdpandmax_diffchange after creation, so both needmut. Without it the compiler rejects the assignments. - Tail expression.
dp[k][n]has no semicolon and noreturn, so it is the function's value.
Complexity
Time: O(k * n). The two loops visit each cell once, and each cell now costs a few comparisons instead of a scan over earlier days.
Space: O(k * n). The table is unchanged. Because row t only reads rows t and t - 1, two rows would be enough if you wanted O(n) space.
Edge cases and common mistakes
- Prices that only fall.
[5, 4, 3]withk = 2gives 0, because the no-sale options keep every cell at 0. - No prices or
k = 0. An empty vector hasn = 0, so the inner loop never runs and the result is 0. Withk = 0the outer loop never runs and the result is 0 as well. - Starting
max_diffat 0. That pretends a free purchase happened before day 1 and can report a profit that doesn't exist. - Large
k. The table hask + 1rows, so an enormouskallocates a lot of memory even though no more thann / 2transactions can be useful.
Testing
prices = [2, 4, 1],k = 2gives2.prices = [3, 2, 6, 5, 0, 3],k = 2gives7.prices = [7, 1, 5, 3, 6, 4],k = 1gives5.prices = [1, 5, 2, 8, 3, 9],k = 3gives16.prices = [],k = 2gives0.
