一、121. 买卖股票的最佳时机 - 力扣(LeetCode)
题目要求: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解法一:贪心
class Solution {
public:
int maxProfit(vector<int>& prices) {
int low = INT_MAX;
int result = 0;
for (int i = 0; i < prices.size(); i++) {
low = min(low, prices[i]); // 取最左最小价格
result = max(result, prices[i] - low); // 直接取最大区间利润
}
return result;
}
};
解法二:动态规划(我自己写的)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len < 2)
return 0;
vector<int>dp(len);//dp[i]的含义是第i天卖出股票所得到的最大收益
dp[0] = 0;
int ans = 0;
for(int i = 1;i < len;i ++)
{
int val = prices[i] - prices[i - 1];
if(val > 0)
{
dp[i] = dp[i - 1] + val;
}else{
dp[i] = dp[i - 1] + val > 0?dp[i - 1] + val:0;
}
ans = max(ans,dp[i]);
}
return ans;
}
};
动态规划(看的题解):
dp[i] [0]表示第i天不持有股票所得最多现金
dp[i] [1]表示第i天持有股票所得最多现金
注意这里说的是“持有”,“持有”不代表就是当天“买入”!也可能是之前就买入了,今天保持持有的状态
确定递推公式:
如果第i天持有股票即dp[i] [1], 那么可以由两个状态推出来
- 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1] [1]
- 第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
那么就应该选所得现金最大的,所以dp[i] [1] = max(dp[i - 1] [1], -prices[i]);
如果第i天不持有股票即dp[i] [0], 也可以由两个状态推出来
- 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1] [0]
- 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1] [1]
同样也取最大的,dp[i] [0] = max(dp[i - 1] [0], prices[i] + dp[i - 1] [1]);
// 版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if (len == 0) return 0;
vector<vector<int>> dp(len, vector<int>(2));
dp[0][1] -= prices[0];
dp[0][0] = 0;
for (int i = 1; i < len; i++) {
dp[i][1] = max(dp[i - 1][1], -prices[i]);
dp[i][0] = max(dp[i - 1][0], prices[i] + dp[i - 1][1]);
}
return dp[len - 1][0];
}
};
二、122. 买卖股票的最佳时机 II - 力扣(LeetCode)
题目要求: 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 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]);//两个来源:之前就一直持有,或者是原本没有持有,刚刚买入的
当然这个题用贪心更好理解。
三、123. 买卖股票的最佳时机 III - 力扣(LeetCode)
题目要求:[定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
这个题比较难,因为有四种状态,思路见注释:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int size = prices.size();
if(size == 1)
return 0;
//分为5种状态:1、没有操作发生。2、第一次买入。3、第一次卖出。4、第二次买入。5、第二次卖出
//没有操作发生的时候,数组的值一定是0,所以就不单独列出来了
vector<vector<int>>dp(size,vector<int>(4));
//下面进行初始化
dp[0][0] = -prices[0];//dp[i][0]代表的是当前持有股票,且买入之前没有发生交易。也就是第一次买入
dp[0][1] = 0;//第一次卖出。卖出肯定是要盈利的,如果不能盈利那就是0。也就是代表了当前没有持有股票,且之前发生了一次交易的最大利润
dp[0][2] = -prices[0];//这里有必要单独解释一下。此时代表了当前持有股票,且买入之前发生了一次交易。也就是第二次买入,第二次买入是在第一次卖出的基础上进行的,第一次卖出初始化为0,所以第二次买入就是-price[0]
dp[0][3] = 0;//代表了当前没有持有股票,之前已经发生过两次交易的最大利润,也就是第二次卖出
for(int i = 1;i < size;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]);
dp[i][2] = max(dp[i - 1][2],dp[i - 1][1] - prices[i]);
dp[i][3] = max(dp[i - 1][3],dp[i - 1][2] + prices[i]);
}
return dp[size - 1][3];//最后一定是第二次卖出的时候利润最大,两次卖出的状态现金最大一定是最后一次卖出。
//因为如果一次卖出就可以获得最大利润的话,那么第二次我们可以选择不买不卖。第二次卖出的情况其实已经包含了第一次卖出的情况
}
};
四、188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
题目要求: 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
解析:
和上一题相比,这一题只是把交易的次数给做成了一个变量,上一题的交易次数是一个常量2。
解题思路是完全一样的。
一共分为2*k + 1种状态,分别是没有操作(利润就是0),第一次买入,第一次卖出,第二次......第k次买入,第k次卖出。
使用二维数组 dp[i] [j] :第i天的状态为j,所剩下的最大现金是dp[i] [j]
j的状态表示为:
- 0 表示不操作
- 1 第一次买入
- 2 第一次卖出
- 3 第二次买入
- 4 第二次卖出
所以,奇数天是买入,偶数天是卖出。
其他思路和上一题一模一样,具体见注释:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
//和上一题相比,可以交易的次数从2变成了k,成为了一个变量而非常量,解题思路并没有什么区别
int len = prices.size();
if(len < 2 || k < 1)
return 0;
vector<vector<int>>dp(len,vector<int>(2*k + 1));//dp[i][j]如果j是奇数,就代表的是第(j/2) + 1次买入,比如dp[0][1]代表第一次买入,dp[0][3]代表第二次买入
//如果j是偶数,就代表j/2次卖出,比如dp[i][2]代表第1次卖出
for(int i = 0;i <= 2*k;i ++)//初始化
{
if(i%2 == 1)//奇数,代表买入
{
dp[0][i] = - prices[0];
}else{
dp[0][i] = 0;
}
}
for(int i = 1;i < len;i ++)//遍历dp数组
{
for(int j = 1;j <= 2*k;j ++)
{
if(j%2 == 1)//奇数,代表买入.可能是早就买入了,也可能是刚刚买入。如果是第i次刚刚买入,那么就应该是第i- 1次卖出的价格减去prices[i]
{
dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - 1] - prices[i]);
}else{
dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - 1] + prices[i]);//偶数代表卖出.可能是早就卖出了,也可能是刚刚卖出。如果是第i次刚刚卖出,那么就应该是第i- 1次买入的价格加上prices[i]
}
}
}
return dp[len - 1][2*k];
}
};
五、309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)
题目要求: 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
解析:
这个题目就是要注意,卖了之后不能立刻就再买入,至少要等一天。当前买入的话,至少要两天之前卖出才行。明白这一点就OK了。主要就在递推公式上
代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len < 2)
return 0;
vector<vector<int>>dp(len,vector<int>(2));//dp[i][0]代表着当前不持有股票(也就是卖出状态)所能获得的最大金额,dp[i][1]代表了当前持有股票(也就是买入状态)所能获得的最大利润
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = max(dp[0][0],prices[1] + dp[0][1]);
dp[1][1] = max(dp[0][1],- prices[1]);
for(int i = 2;i < len;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 - 2][0] - prices[i]);//关键点就在这里,由于存在冷冻期,所以买入的前提必须是至少两天前就完成了交易
}
return dp[len - 1][0];
}
};
六、714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
题目要求: 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
easy:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
if(len < 2)
return 0;
vector<vector<int>>dp(len,vector<int>(2));//还是老规矩,dp[i][0]代表第i天不持有股票的最大利润,dp[i][1]代表持有股票的最大利润
dp[0][0] = 0;
dp[0][1] = - prices[0] - fee;
for(int i = 1;i < len;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] - fee);
}
return dp[len - 1][0];
}
};
这道题还可以用贪心来做,思路见注释:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
if(len < 2)
return 0;
//贪心做法
int back = prices[0];//back指的是在后面那个被减的数
int front = prices[0];//front是前面那个减数,就是更大的数
bool flag = false;//用来标记是否已经交过fee了,交过就是true,就不用再交了
int ans = 0;
for(int i = 1;i < len;i ++)
{
if(prices[i] < back && flag == false)//没教过手续费,说明不在买卖之中,可以更新back
{
back = prices[i];
}
if(prices[i] > back + fee && flag == false)//有利可图
{
ans += prices[i] - back - fee;
back = prices[i];
flag = true;//置为true,这笔交易里后面就不用再交了
}
if(prices[i] > back && flag == true)
{
ans += prices[i] - back;
back = prices[i];
}
if(prices[i] < back - fee)//出现了更小的坡谷,
{
back = prices[i];
flag = false;//要重新开始买卖了。新的买卖要交手续费的
}
}
return ans;
}
};