文章目录
一、动归状态和状态转移方程模板
动归状态和状态转移方程模板:
f
(
i
,
j
,
k
)
:
在
第
i
天
结
束
时
状
态
是
手
里
持
有
0
股
股
票
且
不
处
于
冷
冻
期
(
j
=
0
)
或
手
里
持
有
1
股
股
票
(
j
=
1
)
或
手
里
持
有
0
股
股
票
且
处
于
冷
冻
期
(
j
=
2
)
,
已
经
进
行
了
k
次
交
易
,
此
时
的
最
大
利
润
然
后
自
己
根
据
当
天
的
状
态
是
怎
么
由
前
一
天
操
作
来
的
找
状
态
转
移
方
程
f(i,j,k):在第i天结束时\\ 状态是手里持有0股股票且不处于冷冻期(j = 0)\\ 或手里持有1股股票(j = 1)\\或手里持有0股股票且处于冷冻期(j = 2),\\ 已经进行了k次交易,此时的最大利润\\ 然后自己根据当天的状态是怎么由前一天操作来的\\找状态转移方程
f(i,j,k):在第i天结束时状态是手里持有0股股票且不处于冷冻期(j=0)或手里持有1股股票(j=1)或手里持有0股股票且处于冷冻期(j=2),已经进行了k次交易,此时的最大利润然后自己根据当天的状态是怎么由前一天操作来的找状态转移方程
二、LeetCode121. 买卖股票的最佳时机
动归优化版本
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
f(i,0,0)到第i天 手里持有0股股票 进行了0次交易的最大收益
f(i,1,0)到第i天 手里持有1股股票 进行了0次交易的最大收益
f(i,0,1)到第i天 手里持有0股股票 进行了1次交易的最大收益
f(i,0,0) = 0;
f(i,1,0) = max(f(i - 1, 1, 0), f(i - 1, 0, 0) - prices[i])
f(i,0,1) = max(f(i - 1, 0, 1), f(i - 1, 1, 0) + prices[i])
f(0,1,0) = -prices[0]
f(0,0,1) = 0;
*/
int n = prices.size();
int prev01 = 0, prev10 = -prices[0];
int cur01 = 0, cur10 = -prices[0];
for (int i = 1; i < n ; ++i)
{
cur10 = max(prev10, -prices[i]);
cur01 = max(prev01, prev10 + prices[i]);
prev01 = cur01;
prev10 = cur10;
}
return cur01;
}
};
贪心算法
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
找到当前能买入的最小价格和当前能卖出的最大价格
作差得今天的最大收益
每一天的最大收益中取大即答案
*/
int len = prices.size();
if (len == 1)
{
return 0;
}
int maxprofit = 0;
int curprofit = 0;
int miniprice = INT_MAX;
for (int i = 0; i < len; i++)
{
if (prices[i] < miniprice)
{
miniprice = prices[i];
}
curprofit = prices[i] - miniprice;
if (curprofit > maxprofit)
{
maxprofit = curprofit;
}
}
return maxprofit;
}
};
二、LeetCode122. 买卖股票的最佳时机 II
动归优化版本
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
动态规划:本题和上一个买卖股票的题的最大区别在于本题没有限制交易次数
只是限制了任何时候最多持有一只股票
那么定义dp[i][2]数组,dp[i][0]表示第i天结束时持有0只股票的最大收益值
dp[i][1]表示第i天结束时持有1只股票的最大收益值
那么如果第i天结束时持有1只股票 那就有两种可能
1.第i-1天持有0只股票 第i天购买了股票 dp[i - 1][0] - prices[i]
2.也有可能是第i-1天持有1只股票 第i天没做任何操作
dp[i - 1][1]
总结的状态转转移方程为:
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
如果第i天持有0只股票 那么同样两种可能
1.第i-1天持有1只股票,第i天卖掉了dp[i - 1][1] + prices[i]
2.第i-1天持有0只股票,第i天没操作dp[i - 1][0]
总结状态转移方程为:
dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0])
初始状态dp[0][0]=0 dp[0][1] = -prices[0]
观察到状态转移方程仅和前一项有关系 可以用O(1)的空间复杂度优化
*/
int prev0 = 0;//dp[0][0]
int prev1 = -prices[0];//dp[0][1]
int size = prices.size();
for (int i = 1; i < size; ++i)
{
int cur0 = max(prev1 + prices[i], prev0);
int cur1 = max(prev0 - prices[i], prev1);
prev1 = cur1;
prev0 = cur0;
}
return prev0;
}
};
贪心算法
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
贪心算法:本题可以拆解为寻找x个区间[ri,li]
使得这x个区间的利润和加起来最大
不难发现 每个区间的贡献都等价于组成自己的一个个长度为1的小区间的贡献的求和
[ri,li] = [ri, ri + 1] + [ri + 1, ri + 2] + ... + [li - 1, li]
所以本题可以等价的转化为寻找x个区间[ri, ri + 1]
使得他们的贡献和最大
什么样的区间才有贡献的价值呢?
显然是prices[ri + 1] - prices[ri] > 0的区间
也可以理解为 每一个上升我都收割到了 那样的收入一定是最高的
*/
int size = prices.size();
int ans = 0;
for (int i = 0; i < size - 1; ++i)
{
if (prices[i] < prices[i + 1])
{
ans += prices[i + 1] - prices[i];
}
}
return ans;
}
};
三、LeetCode123. 买卖股票的最佳时机 III
动归未优化版本
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
定义f(i,j,k)表示下标为i的天以前,手里持有j股股票,已经完成了k次交易时的最大收益值
j = 0、1; k = 0, 1, 2
答案显然等于max(f(n - 1,0,k))
状态转移方程:
1.1 f(i,0,0) = f(i - 1, 0, 0) = 0
1.2 f(i,1,0) = max(f(i - 1, 1, 0), f(i - 1, 0, 0) - prices[i])
2.1 f(i,0,1) = max(f(i - 1, 1, 0) + prices[i], f(i - 1, 0, 1))
2.2 f(i,1,1) = max(f(i - 1, 1 ,1), f(i - 1, 0, 1) - prices[i])
3.1 f(i,0,2) = max(f(i - 1, 0, 2), f(i - 1, 1, 1) + prices[i])
3.2 f(i,1,2) 此状态不存在 已经进行了两次交易了 不允许此状态存在 f(i,1,2) = 0
初始条件:
f(i,0,0) = 0; f(0,1,0) = -prices[0]; f(0,1,1) = -prices[0] 当天买入 卖出 再买入
*/
int n = prices.size();
vector<vector<vector<int>>> dp(n, vector<vector<int>>(2, vector<int>(3)));
dp[0][1][0] = -prices[0];
dp[0][1][1] = -prices[0];
for (int i = 1; i < n; ++i)
{
dp[i][1][0] = max(dp[i - 1][1][0], dp[i - 1][0][0] - prices[i]);
dp[i][0][1] = max(dp[i - 1][0][1], dp[i - 1][1][0] + prices[i]);
dp[i][1][1] = max(dp[i - 1][1][1], dp[i - 1][0][1] - prices[i]);
dp[i][0][2] = max(dp[i - 1][0][2], dp[i - 1][1][1] + prices[i]);
}
/*如果最大值出现在一次交易时 那么它一定可以通过当天买入转移到两次交易*/
return dp[n - 1][0][2];
}
};
动归优化版本
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
f(i,0,1) = max(f(i - 1, 0, 1), f(i - 1, 1, 0) + prices[i])
f(i,0,2) = max(f(i - 1, 0, 2), f(i - 1, 1, 1) + prices[i])
f(i,1,0) = max(f(i - 1, 1, 0), f(i - 1, 0, 0) - prices[i])
f(i,1,1) = max(f(i - 1, 1, 1), f(i - 1, 0, 1) - prices[i])
f(i,1,2) = 非法值 直接放着0不管就行
初始条件
f(0,1,0) = -prices[i]
f(0,1,1) = -prices[i]
*/
int f01 = 0;
int f02 = 0;
int f10 = -prices[0];
int f11 = -prices[0];
int size = prices.size();
for (int i = 1; i < size; ++i)
{
/*
这里优化空间复杂度的时候,不需要前继如f01'来代表f(i - 1, 0 ,1)
因为f01更新后比f(i - 1, 0 ,1)多考虑了第i天
假设max落到了第i天,当天买入卖出损益值是0 无影响
*/
f10 = max(f10, -prices[i]);
f01 = max(f01, f10 + prices[i]);
f02 = max(f02, f11 + prices[i]);
f11 = max(f11, f01 - prices[i]);
}
/*
f01 f02初始值都是0 由于取的最大值 它们最终一定大于等于0
如果最大值出现在f01 那么一定可以由于当天可以买入再卖出这一操作转移到f02
*/
return f02;
}
};
四、LeetCode188. 买卖股票的最佳时机 IV
动归优化版本
class Solution {
public:
int maxProfit(int k, vector<int>& prices)
{
if (prices.empty())
{
return 0;
}
/*
one[i][j] 考虑0~i下标天时,当前已经进行了j次交易,本天持有一股股票的最大收益
zero[i][j] 考虑0~i下标天时,当前已经进行了j次交易,本天持有0股股票的最大收益
显然 one[i][j] = max(one[i - 1][j], zero[i - 1][j] - prices[i])
zero[i][j] = max(zero[i - 1][j], one[i - 1][j - 1] + prices[i])
初始条件 one[0][j] = -prices[0]
zero[0][j] = 0
*/
int n = prices.size();
vector<int> one(k + 1, -prices[0]);
vector<int> zero(k + 1);
for (int i = 1; i < n; ++i)
{
/*这里对应one[i][0] = max(one[i - 1][0], zero[i - 1][0] - prices[0])*/
/*zero[i][0]不用更新 第i天进行0次交易 最大收益肯定是0*/
one[0] = max(one[0], zero[0] - prices[i]);
for (int j = 1; j <= k; ++j)
{
one[j] = max(one[j], zero[j] - prices[i]);
zero[j] = max(zero[j], one[j - 1] + prices[i]);
}
}
return zero[k];
}
};
五、LeetCode309. 最佳买卖股票时机含冷冻期
动归优化版本
class Solution {
public:
int maxProfit(vector<int>& prices)
{
/*
f(i,j)表示第i天结束时,持有一只股票(j = 1)
或不持有股票且不处于冷冻期(j = 0)
或不持有股票且处于冷冻期(j = 2)的当前最大收益
状态转移方程:
f(i, 2) = f(i - 1, 1) + prices[i] 今天结束时处于冷冻期一定是因为今天卖出了股票
f(i, 1) = max(f(i - 1, 1), f(i - 1, 0) - prices[i])
f(i, 0) = max(f(i - 1, 0), f(i - 1, 2))
答案就是max(f(n - 1,0), f(n - 1, 2))
初始条件 f(0, 0) = 0 f(0, 1) = -prices[0]
f(0, 2) = 0 第0天买入又卖出
*/
int n = prices.size();
int prev0 = 0, prev1 = -prices[0], prev2 = 0;
int cur0 = prev0, cur1 = prev1, cur2 = prev2;
for (int i = 1; i < n; ++i)
{
cur0 = max(prev0, prev2);
cur1 = max(prev1, prev0 - prices[i]);
cur2 = prev1 + prices[i];
prev0 = cur0;
prev1 = cur1;
prev2 = cur2;
}
return max(cur0, cur2);
}
};
六、LeetCode714. 买卖股票的最佳时机含手续费
动态规划优化版本
class Solution {
public:
int maxProfit(vector<int>& prices, int fee)
{
/*
dp[i][0]表示第i天结束时不持有股票的最大收益
dp[i][1]表示第i天结束时持有1股股票的最大收益
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
初始条件:dp[0][0] = 0; dp[0][1] = -prices[0]
*/
int prev0 = 0, prev1 = -prices[0];
int cur0 = prev0, cur1 = prev1;
int n = prices.size();
for (int i = 1; i < n; ++i)
{
cur0 = max(prev0, prev1 + prices[i] - fee);
cur1 = max(prev1, prev0 - prices[i]);
prev0 = cur0;
prev1 = cur1;
}
return cur0;
}
};
贪心算法
class Solution {
public:
int maxProfit(vector<int>& prices, int fee)
{
/*
这里与买卖股票II唯一的不同就是有手续费
那时我们把连续上涨时在最高点套现利用可以当天买入再卖出转化为了
只要明天的价格大于今天 就买入并卖出
对应到本题 首先 平时的买入价格看做是基础价格加上小费
如果明天卖出能盈利 那么立马卖出 但是为了能在最高点套现
提供一个后悔机制 同时我们再以prices[i + 1]的价格买入股票而不是以price[i + 1] + fee的价格买入
这样后面再是高点的话就可以套现了
比如i~j是连续上涨 i是最低点 j是最高点
i点买入的价格是prices[i] + fee
i + 1有盈利 即prices[i + 1] - prices[i] - fee > 0
卖出 盈利profit = prices[i + 1] - prices[i] - fee
再以prices[i + 1]买入 然后prices[i + 2] - prices[i + 1] > 0
有盈利空间 卖出 prices[i + 2] - prices[i + 1] + prices[i + 1] - prices[i] - fee
= prices[i + 2] - prices[i + 1] - fee
所以最高点套现后 利润等价于prices[j] - prices[i] - fee
*/
int buy = prices[0] + fee;
int profit = 0;
int n = prices.size();
for (int i = 0; i < n - 1; ++i)
{
if (prices[i] + fee < buy)
{
buy = prices[i] + fee;
}
if (prices[i + 1] > buy)
{
profit += prices[i + 1] - buy;
buy = prices[i + 1];
}
}
return profit;
}
};