题目描述

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3。
最大总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

题目思路

前面还有一题 121. 买卖股票的最佳时机 比较简单没有记录,其条件是只允许买入一次和卖出一次,这里是可以多次买入和卖出。可以把每次买入和卖出都看成是一次交易。

贪心算法

而这里题干中说了在每一天可以决定是否购买和/或出售股票,实际上只需要在价格上涨前时候买入,在价格下跌前卖出就可以了。也就是在每次价格上涨的时候都进行一次交易。

所以这题第一反应是贪心算法,也就是每次只要价格上涨就进行交易。可以遍历整个数组,判断当前价格和前一个价格的大小关系,如果当前价格大于前一个价格,就将差值累加到结果中。

1
2
3
4
5
6
7
8
9
10
11
func maxProfit(prices []int) int {
maxProfit := 0

for i := 1; i < len(prices); i++ {
if profit := prices[i] - prices[i-1]; profit > 0 {
maxProfit += profit
}
}

return maxProfit
}

这里其实也就是找到这个数组里每一个长度为 2 的上升子序列,然后累加子序列的差值即可。即便📈连续上升,也不会影响结果,比如 [1,2,3,4,5],在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。而如果累加 2-13-24-35-4,也会得到 4。所以这里的贪心算法是正确的。

动态规划

如果不使用贪心算法的话,可以使用动态规划的方法。说实话这个题目用动态规划的方法来做有点大材小用。因为这里的贪心算法已经可以很简单的解决这个问题了。但遇到动态规划的问题还是要考虑动态规划的方法。动态规划的核心思想是将一个复杂的问题分解成简单的子问题,然后通过解决子问题来解决原问题。这里其实最关键的就是要想到:

  • 每天手上的股票只会有两种状态:持有股票和不持有股票。也就是 dp[i][0] 表示第 i 天不持有股票的最大利润,dp[i][1] 表示第 i 天持有股票的最大利润。
  • i 天不持有股票的状态只会通过两种状态转移过来:第 i-1 天没有持有股票且第 i 天也没有买入股票,或者第 i-1 天持有股票而第 i 天卖出股票。也就是 dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
  • i 天持有股票的状态只会通过两种状态转移过来:第 i-1 天持有股票且第 i 天也没有卖出股票,或者第 i-1 天没有持有股票而第 i 天买入股票。也就是 dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
  • 这里的 dp[i][0]dp[i][1] 的初始值是 0-prices[0],也就是第 0 天不持有股票的最大利润为 0,持有股票的最大利润为 -prices[0]
  • 最终的结果就是 dp[len(prices)-1][0],也就是最后一天不持有股票的最大利润。

所以这里可以写出代码:

1
2
3
4
5
6
7
8
9
10
func maxProfit(prices []int) int {
dp := make([][]int, len(prices))
dp[0] = []int{0, -prices[0]}

for i := 1; i < len(prices); i++ {
dp[i] = []int{max(dp[i-1][1]+prices[i], dp[i-1][0]), max(dp[i-1][1], dp[i-1][0]-prices[i])}
}

return dp[len(prices)-1][0]
}

当然还可以优化,上面代码时间复杂度是 O(n),空间复杂度是 O(1),但是因为只需要保存前一天的状态,所以可以只用两个变量来保存前一天的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func max(a, b int) int {
if a > b {
return a
}

return b
}

func maxProfit(prices []int) int {
dp := []int{0, -prices[0]}

for i := 1; i < len(prices); i++ {
dp = []int{max(dp[1]+prices[i], dp[0]), max(dp[1], dp[0]-prices[i])}
}

return dp[0]
}

这样就可以将空间复杂度降到 O(1) 了。