题目链接 | 理论基础
本题只能买卖一次股票,也就是找最大的前后差:找到左边最小的,找到右边最大的,得到的最大非负差值就是最大利润。
不过虽然本题的贪心算法非常直白,要想写出 O ( n ) O(n) O(n) 的正确贪心解也不容易,具体思路有点像双指针。(即使知道是贪心也不好写啊!太笨了)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
left_lowest = float('inf')
result = 0
for i in range(len(prices)):
left_lowest = min(left_lowest, prices[i])
result = max(result, prices[i] - left_lowest)
return result
贪心虽好,可不要贪心呦。对于这种题目,dp 才是无往不利的王道。
相比于之前的打家劫舍中的一维数组,股票类问题需要记录之前一步的状态。因为在打家劫舍中,当前 i 的状态可以确定地从之前的某个状态中推导得到(抢 i
那么一定不会抢 i-1
,不抢 i
那么就和 i-1
的结果相同)。然而,股票问题的状态是不能通过之前的状态确定推导的:当前能否卖出股票取决于之前是否持有股票,只记录之前得到的最大收益就无从得知对于股票的持有状态。所以,本题需要靠一个二维数组来同时记录(状态,当前收益)。
dp 数组的下标含义:注意这里所代表的是最大净利润,当然也可能是负数
dp[i][0]
:如果第 i
天结束的时候不持有股票,能够得到的最大利润dp[i][1]
:如果第 i
天结束的时候持有股票,能够得到的最大利润dp 递推公式:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
,如果第 i
天结束时不持有股票,可能的情况有两种:
i-1
天的情况完全相同,dp[i-1][0]
i
天内将股票卖出,dp[i-1][1] + prices[i]
dp[i][1] = max(dp[i-1][1], - prices[i])
,如果第 i
天结束时持有股票,可能的情况有两种:
i-1
天的情况完全相同,dp[i-1][1]
i
天内买入股票,由于本题只允许进行一次买入,那么当前的净利润必然是 - prices[i]
dp 数组的初始化:从递推公式可知,我们需要初始化第一天的 dp 数组,其余值设为 0 即可(可以处理股票价格一直下跌的状态)
dp[0][0]
:第一天开始时没有股票,第一天结束时没有股票,那么净利润还是 0dp[0][1]
:第一天开始时没有股票,第一天结束时持有股票,那么净利润是 -prices[0]
dp 的遍历顺序:终于没有背包问题的复杂顺序哲学了,明显的从小到大遍历
举例推导:
i | dp[i][0] |
dp[i][1] |
---|---|---|
7 | 0 | -7 |
1 | 0 | -1 |
5 | 4 | -1 |
3 | 4 | -1 |
6 | 5 | -1 |
4 | 5 | -1 |
值得一提的是,一般来说完成 dp 数组的遍历后,应该返回 max(dp[-1][0], dp[-1][1])
。但根据简单的逻辑,到了最后一天无论如何都是手中没有股票的净利润更高,毕竟不卖股票的话肯定赚不了钱,所以返回 dp[-1][0]
即可。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# dp[i][0] represents the max profit without holding a stock at hand on day i
# dp[i][1] represents the max profit while holding a stock at hand on day i
dp = [[0, 0] for _ in range(len(prices))]
# initialization
dp[0] = [0, -prices[0]]
for i in range(1, len(prices)):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], - prices[i])
return max(dp[-1][0], dp[-1][1])
本题的时间复杂度自然是 O ( n ) O(n) O(n),空间复杂度也是 O ( n ) O(n) O(n)。
观察我们在遍历中经历的递推,很容易发现 dp[i]
只依赖于 dp[i-1]
,所以并不需要维护整个 n × 2 n\times2 n×2 的数组,只需要一个 2 × 2 2\times2 2×2 的滚动数组即可,依靠天数 i 对 2 的取余即可。
以下代码直接复制自代码随想录。
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], -prices[i])
dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1) % 2][0])
return dp[(length-1) % 2][1]
题目链接 | 理论基础
本题的条件和上一唯一的不同在于股票可以多次买卖了(但是同时只能持有一支股票),其余条件都一样,甚至于本题也有一种贪心解法。
这个条件上的区别导致的解法的变化在于 dp 的递推公式:dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
:
-prices[i]
。prices[i]
,但却不知道之前未持有股票的利润是多少,所以只能知道当前的净利润是 dp[i-1][0] - prices[i]
。class Solution:
def maxProfit(self, prices: List[int]) -> int:
# dp[i][0] represents the max profit without holding the stock on day i
# dp[i][1] represents the max profit while holding the stock on day i
dp = [[0, 0] for _ in range(len(prices))]
dp[0] = [0, -prices[0]]
# dp formula
for i in range(1, len(prices)):
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 max(dp[-1][0], dp[-1][1])