算法刷题打卡042 | 动态规划10
这一天的题目内容开启了买卖股票最佳时机的专题,两道最基础也是最经典的题目复习回顾买卖股票相关的动态规划问题。
LeetCode 121 买卖股票的最佳时机
题目链接:121. 买卖股票的最佳时机 - 力扣(Leetcode)
买卖股票的第一个版本是只交易两次,分别是买入以及在买入之后的卖出。由于只有正利润才符合题目预期结果,当股票价格呈现非递增变化时,买卖股票无法获得收益(甚至有可能亏损),因此可以贪心地找左端最小值及其对应的右端最大值,取其中最大的差值。具体做法是遍历每天的股票价格,不断更新左端的最小值,同时计算当前价格与最小值的差值,更新结果:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 贪心写法
minprice = prices[0]
maxprofit = 0
for i in range(1, len(prices)):
maxprofit = max(maxprofit, prices[i]-minprice)
minprice = min(minprice, prices[i])
return maxprofit
而用动态规划分析题目,按时间顺序,每一天的最大利润和前一天的状态息息相关,存在一个状态推导的过程。具体状态的定义比较讲究对问题的理解,比如对于股票的买卖,有持有和不持有两种状态,他们都能通过保持原有状态直接继承前一天的状态,也可以通过买卖操作更新,并且按题目要求取二者的最大值:
#include<algorithm>
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
// dp[i][0] 持有;dp[i][1] 不持有
vector<vector<int>> dp(n, vector<int>(2));
// 初始化
dp[0][0] = -prices[0];
for(int i=1; i < n; i++){
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][0] = max(dp[i-1][0], -prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]);
}
return max(0, dp[n-1][1]); // 最终要求的是最后一天不持有股票的状态(如果为负利润返回0)
}
};
要注意本题只能进行一次买入卖出,因此dp[i][0]更新时,当天买入一定是-prices[i],而不是像注释的那样从前一天不持有的状态减去prices[i],因为如果前一天属于已经交易完的不持有状态,在它的基础上买入就违反了买卖规则。实际上注释写法应该是属于不限制买卖次数的情境。
状态推导过程中只依赖前一天的状态,因此空间复杂度可进一步优化,不再赘述。
LeetCode 122 买卖股票的最佳时机II
题目链接:122. 买卖股票的最佳时机 II - 力扣(Leetcode)
这一题其实就是在上一题的基础上不限制买卖次数,题目的“最多只能持有一股股票”,其实就是要求每次买入前必须保证手头没有持有股票,也就是下一次买入前必须经过卖出操作,沿用上一题的代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
// dp[i][0] 持有;dp[i][1] 不持有
vector<vector<int>> dp(n, vector<int>(2));
// 初始化
dp[0][0] = -prices[0];
for(int i=1; i < n; i++){
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);
// dp[i][0] = max(dp[i-1][0], -prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]);
}
return max(0, dp[n-1][1]); // 最终要求的是最后一天不持有股票的状态(如果为负利润返回0)
}
};
贪心的解法就是遍历找所有递增区间,在每个递增区间的起点和终点分别进行买入和卖出,可以保证利润最大化:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
if n < 2:
return 0 # 无法进行交易
# idx, total = 0, 0
# # 方法是找上升区间
# while idx < n:
# while idx < n - 1 and prices[idx] > prices[idx + 1]:
# idx += 1
# min_price = prices[idx]
# while idx < n - 1 and prices[idx] < prices[idx + 1]:
# idx += 1
# total += prices[idx] - min_price
# idx += 1
# return total
total = 0
for i in range(1, n):
total += max(0, prices[i] - prices[i-1])
return total