|
| 1 | +/** |
| 2 | + * [188] Best Time to Buy and Sell Stock IV |
| 3 | + * |
| 4 | + * Say you have an array for which the i^th element is the price of a given stock on day i. |
| 5 | + * |
| 6 | + * Design an algorithm to find the maximum profit. You may complete at most k transactions. |
| 7 | + * |
| 8 | + * Note:<br /> |
| 9 | + * You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). |
| 10 | + * |
| 11 | + * Example 1: |
| 12 | + * |
| 13 | + * |
| 14 | + * Input: [2,4,1], k = 2 |
| 15 | + * Output: 2 |
| 16 | + * Explanation: Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2. |
| 17 | + * |
| 18 | + * |
| 19 | + * Example 2: |
| 20 | + * |
| 21 | + * |
| 22 | + * Input: [3,2,6,5,0,3], k = 2 |
| 23 | + * Output: 7 |
| 24 | + * Explanation: Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4. |
| 25 | + * Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. |
| 26 | + * |
| 27 | + */ |
| 28 | +pub struct Solution {} |
| 29 | + |
| 30 | +// submission codes start here |
| 31 | + |
| 32 | +/* |
| 33 | + 已经在 #123 里解过了, 为了方便阅读直接把那题的分析拷贝到这里 |
| 34 | +
|
| 35 | + 先考虑只进行 1 次交易的情况, 我们求以 i *为售出点*, 只进行 1 次交易获得的最大利润, 那么: |
| 36 | +
|
| 37 | + f[i] = if f[i-1] > 0 { f[i-1] } else { 0 } + prices[i] - prices[i-1] |
| 38 | +
|
| 39 | + 这很容易解, 解完之后找出 f 里的最大值即可, 但这不容易推广到 K 次交易的情况, 因为这时 f[i] 不代表到 i *为止*的最大利润, 无法作为单独的交易帮助递推 |
| 40 | + (到 i 为止的含义是售出点可以在 [0,i] 之间) |
| 41 | +
|
| 42 | + 我们可以稍作改进, 变成求以 i 为结束点, 只进行 1 次交易获得的最大利润, 那么: |
| 43 | +
|
| 44 | + f[i] = max( |
| 45 | + f[i-1], |
| 46 | + prices[i] - min(prices[j] { j in [0, i-1] }) |
| 47 | + ) |
| 48 | +
|
| 49 | + 这仍然是一个 O(N) 的解法, 因为 min(prices[j] { j in [0, i-1] }) 不需要遍历, 可以在递推过程中直接维护好 |
| 50 | +
|
| 51 | + 现在再推广到进行 K 次交易的情况, 那我们要求以 i 为结束点, 进行 k 次交易获得的最大利润, 这时有了变化, 我们可以在 j 之前再进行 K - 1 次交易: |
| 52 | +
|
| 53 | + f[k, i] = max( |
| 54 | + f[k, i-1], |
| 55 | + prices[i] + max(f[k-1, j] - prices[j]) { j in [0, i-1] } ) |
| 56 | + ) |
| 57 | +
|
| 58 | + 显然, f[0, i] = 0, f[k, 0] = 0 |
| 59 | +
|
| 60 | + 这个算法可以形象地描述一下, 在 k = 1 时, 我们每次要找的就是 i 之前的最低谷点作为这次交易的开始点 j, 而当 k > 1 时, |
| 61 | + 我们 i 之前就有可能已经进行过交易了, 这时我们在找开始点 j 时, 就要同时考虑 "直到 j 为止, k-1 次交易的最大收益" - "j 本身的值". 以此来找到一个最佳点 j |
| 62 | +
|
| 63 | + 在实现时, 假如用 Bottom-Up 递推, 那么只需要维护一个 vec[i], 因为每轮递推时只会考虑上一轮的数据, 我们可以复用这个 O(N) 的额外存储空间 |
| 64 | +
|
| 65 | + 最后, 这题会给 k 非常大的 corner case, 实际上 k 大于 prices.len() / 2 后面就没有意义了, 可以 shortcut 掉(== 允许无穷次交易的场景), 下面写的比较糙, |
| 66 | + 直接限制了一下循环次数, 实际跑的时候运行时间会长一点 |
| 67 | + */ |
| 68 | +impl Solution { |
| 69 | + pub fn max_profit(k: i32, prices: Vec<i32>) -> i32 { |
| 70 | + if prices.is_empty() { return 0 } |
| 71 | + let max_trans = k as usize; |
| 72 | + let mut cache = vec![0; prices.len()]; |
| 73 | + for _ in 0..usize::min(max_trans, prices.len() / 2 + 1) { |
| 74 | + // best_by_in 维护了考虑前 N 次交易的最佳的买入点, 即 max(f[k-1, j] - prices[j]) { j in [0, i-1] } |
| 75 | + let mut best_buy_in = cache[0] - prices[0]; |
| 76 | + for i in 1..prices.len() { |
| 77 | + // 复用 vec 前暂存一下前一次的计算结果 |
| 78 | + let temp = cache[i]; |
| 79 | + cache[i] = i32::max(cache[i-1], best_buy_in + prices[i]); |
| 80 | + // 更新 best_buy_in |
| 81 | + best_buy_in = i32::max(best_buy_in, temp - prices[i]); |
| 82 | + } |
| 83 | + } |
| 84 | + return *cache.last().unwrap() |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +// submission codes end |
| 89 | + |
| 90 | +#[cfg(test)] |
| 91 | +mod tests { |
| 92 | + use super::*; |
| 93 | + |
| 94 | + #[test] |
| 95 | + fn test_188() { |
| 96 | + assert_eq!(Solution::max_profit(2, vec![3,2,6,5,0,3]), 7); |
| 97 | + } |
| 98 | +} |
0 commit comments