* [做项目(多个C++、Java、Go、测开、前端项目)](https://www.programmercarl.com/other/kstar.html) * [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html) * [背八股(40天挑战高频面试题)](https://www.programmercarl.com/xunlian/bagu.html) # 122.买卖股票的最佳时机II [力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 * 示例 1: * 输入: [7,1,5,3,6,4] * 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 * 示例 2: * 输入: [1,2,3,4,5] * 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 * 示例 3: * 输入: [7,6,4,3,1] * 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 提示: * 1 <= prices.length <= 3 * 10 ^ 4 * 0 <= prices[i] <= 10 ^ 4 ## 算法公开课 **[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[动态规划,股票问题第二弹 | LeetCode:122.买卖股票的最佳时机II](https://www.bilibili.com/video/BV1D24y1Q7Ls),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 本题我们在讲解贪心专题的时候就已经讲解过了[贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html),只不过没有深入讲解动态规划的解法,那么这次我们再好好分析一下动规的解法。 本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的唯一区别是本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票) **在动规五部曲中,这个区别主要是体现在递推公式上,其他都和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)一样一样的**。 所以我们重点讲一讲递推公式。 这里重申一下dp数组的含义: * dp[i][0] 表示第i天持有股票所得现金。 * dp[i][1] 表示第i天不持有股票所得最多现金 如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来 * 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0] * 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i] **注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况**。 在[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。 而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。 那么第i天持有股票即dp[i][0],如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]。 再来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来 * 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1] * 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0] **注意这里和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!** 代码如下:(注意代码中的注释,标记了和121.买卖股票的最佳时机唯一不同的地方) ```CPP class Solution { public: int maxProfit(vector& prices) { int len = prices.size(); vector> dp(len, vector(2, 0)); dp[0][0] -= prices[0]; dp[0][1] = 0; for (int i = 1; i < len; i++) { dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。 dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]); } return dp[len - 1][1]; } }; ``` * 时间复杂度:O(n) * 空间复杂度:O(n) 大家可以本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在: ``` dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); ``` **这正是因为本题的股票可以买卖多次!** 所以买入股票的时候,可能会有之前买卖的利润即:dp[i - 1][1],所以dp[i - 1][1] - prices[i]。 想到到这一点,对这两道题理解的就比较深刻了。 这里我依然给出滚动数组的版本,C++代码如下: ```CPP // 版本二 class Solution { public: int maxProfit(vector& prices) { int len = prices.size(); vector> dp(2, vector(2)); // 注意这里只开辟了一个2 * 2大小的二维数组 dp[0][0] -= prices[0]; dp[0][1] = 0; for (int i = 1; i < len; i++) { dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]); dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]); } return dp[(len - 1) % 2][1]; } }; ``` * 时间复杂度:O(n) * 空间复杂度:O(1) ## 其他语言版本 ### Java: ```java // 动态规划 class Solution // 实现1:二维数组存储 // 可以将每天持有与否的情况分别用 dp[i][0] 和 dp[i][1] 来进行存储 // 时间复杂度:O(n),空间复杂度:O(n) public int maxProfit(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; // 创建二维数组存储状态 dp[0][0] = 0; // 初始状态 dp[0][1] = -prices[0]; for (int i = 1; i < n; ++i) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // 第 i 天,没有股票 dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); // 第 i 天,持有股票 } return dp[n - 1][0]; // 卖出股票收益高于持有股票收益,因此取[0] } } ``` ```java //DP using 2*2 Array (下方還有使用一維滾動數組的更優化版本) class Solution { public int maxProfit(int[] prices) { int dp[][] = new int [2][2]; //dp[i][0]: holding the stock //dp[i][1]: not holding the stock dp[0][0] = - prices[0]; dp[0][1] = 0; for(int i = 1; i < prices.length; i++){ dp[i % 2][0] = Math.max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]); dp[i % 2][1] = Math.max(dp[(i - 1) % 2][1], dp[(i - 1) % 2][0] + prices[i]); } return dp[(prices.length - 1) % 2][1]; } } ``` ```java // 优化空间 class Solution { public int maxProfit(int[] prices) { int[] dp = new int[2]; // 0表示持有,1表示卖出 dp[0] = -prices[0]; dp[1] = 0; for(int i = 1; i <= prices.length; i++){ // 前一天持有; 既然不限制交易次数,那么再次买股票时,要加上之前的收益 dp[0] = Math.max(dp[0], dp[1] - prices[i-1]); // 前一天卖出; 或当天卖出,当天卖出,得先持有 dp[1] = Math.max(dp[1], dp[0] + prices[i-1]); } return dp[1]; } } ``` ### Python: > 版本一: ```python class Solution: def maxProfit(self, prices: List[int]) -> int: length = len(prices) dp = [[0] * 2 for _ in range(length)] dp[0][0] = -prices[0] dp[0][1] = 0 for i in range(1, length): dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]) #注意这里是和121. 买卖股票的最佳时机唯一不同的地方 dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]) return dp[-1][1] ``` > 版本二: ```python class Solution: def maxProfit(self, prices: List[int]) -> int: length = len(prices) dp = [[0] * 2 for _ in range(2)] #注意这里只开辟了一个2 * 2大小的二维数组 dp[0][0] = -prices[0] dp[0][1] = 0 for i in range(1, length): dp[i % 2][0] = max(dp[(i-1) % 2][0], dp[(i-1) % 2][1] - prices[i]) dp[i % 2][1] = max(dp[(i-1) % 2][1], dp[(i-1) % 2][0] + prices[i]) return dp[(length-1) % 2][1] ``` ### Go: ```go // 买卖股票的最佳时机Ⅱ 动态规划 // 时间复杂度:O(n) 空间复杂度:O(n) func maxProfit(prices []int) int { dp := make([][]int, len(prices)) status := make([]int, len(prices) * 2) for i := range dp { dp[i] = status[:2] status = status[2:] } dp[0][0] = -prices[0] for i := 1; i < len(prices); i++ { dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]) } return dp[len(prices) - 1][1] } func max(a, b int) int { if a > b { return a } return b } ``` ```go // 动态规划 版本二 滚动数组 func maxProfit(prices []int) int { dp := [2][2]int{} // 注意这里只开辟了一个2 * 2大小的二维数组 dp[0][0] = -prices[0] dp[0][1] = 0 for i := 1; i < len(prices); i++ { dp[i%2][0] = max(dp[(i-1)%2][0], dp[(i - 1) % 2][1] - prices[i]) dp[i%2][1] = max(dp[(i-1)%2][1], dp[(i-1)%2][0] + prices[i]) } return dp[(len(prices)-1)%2][1] } func max(x, y int) int { if x > y { return x } return y } ``` ### JavaScript: ```javascript // 方法一:动态规划(dp 数组) const maxProfit = (prices) => { let dp = Array.from(Array(prices.length), () => Array(2).fill(0)); // dp[i][0] 表示第i天持有股票所得现金。 // dp[i][1] 表示第i天不持有股票所得最多现金 dp[0][0] = 0 - prices[0]; dp[0][1] = 0; for(let i = 1; i < prices.length; i++) { // 如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来 // 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0] // 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i] dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i]); // 在来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来 // 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1] // 第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0] dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]); } return dp[prices.length -1][1]; }; // 方法二:动态规划(滚动数组) const maxProfit = (prices) => { // 滚动数组 // have: 第i天持有股票最大收益; notHave: 第i天不持有股票最大收益 let n = prices.length, have = -prices[0], notHave = 0; for (let i = 1; i < n; i++) { have = Math.max(have, notHave - prices[i]); notHave = Math.max(notHave, have + prices[i]); } // 最终手里不持有股票才能保证收益最大化 return notHave; } ``` ### TypeScript: > 动态规划 ```typescript function maxProfit(prices: number[]): number { /** dp[i][0]: 第i天持有股票 dp[i][1]: 第i天不持有股票 */ const length: number = prices.length; if (length === 0) return 0; const dp: number[][] = new Array(length).fill(0).map(_ => []); dp[0] = [-prices[0], 0]; for (let i = 1; i < length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]); } return dp[length - 1][1]; }; ``` > 贪心法 ```typescript function maxProfit(prices: number[]): number { let resProfit: number = 0; for (let i = 1, length = prices.length; i < length; i++) { if (prices[i] > prices[i - 1]) { resProfit += prices[i] - prices[i - 1]; } } return resProfit; }; ``` ### C#: > 贪心法 ```csharp public class Solution { public int MaxProfit(int[] prices) { int res = 0; for (int i = 1; i < prices.Length; i++) res += Math.Max(0, prices[i] - prices[i-1]); return res; } } ``` > 动态规划 ```csharp public class Solution { public int MaxProfit(int[] prices) { int[] dp = new int[2]; dp[0] = -prices[0]; for (int i = 1; i < prices.Length; i++) { dp[0] = dp[0]>dp[1] - prices[i]?dp[0]:dp[1] - prices[i]; dp[1] = dp[1] > dp[0] + prices[i] ? dp[1] : dp[0] + prices[i]; } return dp[1]; } } ``` ### C: > 动态规划 ```c #define max(a, b) ((a) > (b) ? (a) : (b)) int maxProfit(int* prices, int pricesSize){ int **dp = malloc(sizeof (int *) * pricesSize); for (int i = 0; i < pricesSize; ++i) { dp[i] = malloc(sizeof (int ) * 2); } // 0表示持有该股票所得最大,1表示不持有所得最大 dp[0][0] = -prices[0]; dp[0][1] = 0; for (int i = 1; i < pricesSize; ++i) { dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]); } return dp[pricesSize - 1][1]; } ``` > 贪心 ```c int maxProfit(int* prices, int pricesSize) { if(pricesSize == 0){ return 0; } int result = 0; for(int i = 1; i < pricesSize; i++){ // 如果今天股票价格大于昨天,代表有利润 if(prices[i] > prices[i - 1]){ result += prices[i] - prices[i - 1]; } } return result; } ``` ### Rust: > 贪心 ```rust impl Solution { pub fn max_profit(prices: Vec) -> i32 { let mut result = 0; for i in 1..prices.len() { result += (prices[i] - prices[i - 1]).max(0); } result } } ``` >动态规划 ```rust impl Solution { pub fn max_profit(prices: Vec) -> i32 { let mut dp = vec![vec![0; 2]; prices.len()]; dp[0][0] = -prices[0]; for i in 1..prices.len() { dp[i][0] = dp[i - 1][0].max(dp[i - 1][1] - prices[i]); dp[i][1] = dp[i - 1][1].max(dp[i - 1][0] + prices[i]); } dp[prices.len() - 1][1] } } ``` > 优化 ```rust impl Solution { pub fn max_profit(prices: Vec) -> i32 { let mut dp = vec![-prices[0], 0]; for p in prices { dp[0] = dp[0].max(dp[1] - p); dp[1] = dp[1].max(dp[0] + p); } dp[1] } } ```