<p align="center">
<a href="https://www.programmercarl.com/xunlian/xunlianying.html" target="_blank">
  <img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>，贡献其他语言版本的代码，拥抱开源，让更多学习算法的小伙伴们受益！</strong></p>

# 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<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(len, vector<int>(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<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(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>) -> 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>) -> 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>) -> 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]
    }
}
```


<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
  <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>
