121: I题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
这系列通用题解见最后
思想: 动态规划
dp[i]表示前i天的最大利润。
- 步骤:
- 确定dp数组以及下标的含义;
- 确定初始条件,如 dp(0);
前0天,前1天最大利润dp[0],dp[1]均为0。 - 根据 dp(i)和 dp(i-1)的关系得出状态转移方程;
dp[i] = max(dp[i - 1], prices[i] - minprice);
- 求最值
I题解
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0; // 边界条件
int minprice = prices[0];
vector<int> dp (n, 0);
for (int i = 1; i < n; i++){
minprice = min(minprice, prices[i]);
dp[i] = max(dp[i - 1], prices[i] - minprice);
}
return dp[n - 1];
}
};
122: II题目描述:
你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔
思想:贪心算法
每次选择对于当前最好.如果当天股价高于前一天,则前一天买入,当天卖出.
II题解
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxProfit = 0;
for(int i = 1; i < prices.size(); i++)
{
maxProfit += max(prices[i] - prices[i-1], 0);
}
return maxProfit;
}
};
动态规划思想参见暴力搜索、贪心算法、动态规划方法三.
动态规划步骤
第1步:定义状态
状态dp[i][j]定义如下:
- 第一维 i 表示索引为 i 的那一天(具有前缀性质,即考虑了之前天数的收益)能获得的最大利润;
- 第二维 j 表示索引为 i 的那一天是持有股票,还是持有现金。这里 0 表示持有现金(cash),1 表示持有股票(stock)。
第2步:定义状态转移方程
第i天仅有两种状态:持有或者不持有.
- 持有状态
- 今天买入:则昨天是不持有的状态,当天利益为dp[i-1][0]-prices[i];
- 前一天也持有:今天没有卖出,当天利益为dp[i-1][1];取其大者作为当天持有状态下的利益。
- 非持有状态
- 今天卖出:则昨天是持有的状态,当天利益为dp[i-1][1]+prices[i];
- 前一天也不持有:则今天也没有买入,当天利益就是前一天利益,即dp[i-1][0];
取其大者作为当天非持有状态下的利益。
第3步:确定起始状态
- 如果什么都不做,dp[0][0] = 0;
- 如果买入股票,当前收益是负数,即 dp[0][1] = -prices[0];
第4步:确定终止
- 终止的时候,上面也分析了,输出 dp[len - 1][0],因为一定有 dp[len - 1][0] > dp[len - 1][1]。
II题解
class Solution {
public:
int maxProfit(vector<int>& prices) {
// 动态规划解法
// 0.初始判断
if(prices.empty()) return 0;
int n = prices.size();
if( n == 1) return 0;
int dp[prices.size()][2];
// 1.初始状态
dp[0][0] = 0;
dp[0][1] = -prices[0];
// 2.状态转移
for(int i = 1; i<prices.size();i++){
// 3.状态转移方程
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 dp[prices.size()-1][0];
}
};
123: III 题目描述:
你最多可以完成两笔交易。
通用题解
思想:动态规划.
初值
初始状态分为两种,第一种是任何一天,交易次数至多0笔;第二种是第0天有至多1到k_max笔交易数.
dp[i][0][0] = 0,dp[i][0][1] = -infinity;
// 任何一天,至多0笔交易,不持有时最大利润为0,持有则不存在这种情况.
dp[0][k][0] = 0,dp[0][k][1] = -prices[0];
//第0天有至多1到k_max笔交易数.不持有时最大利润为0,持有意味着需要买入,则最大利润为-prices[0].
代码实现:
for(int i = 0; i < len; i ++ ) dp[i][0][0] = 0, dp[i][0][1] = INT_MIN;
for(int k = 1; k <= k_max; k ++ ) dp[0][k][0] = 0, dp[0][k][1] = -prices[0];
转移方程
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
//解释:今天我没有持有股票,有两种可能:
//要么是我昨天就没有持有,我今天还是没有持有;
//要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
//解释:今天我持有着股票,有两种可能:
//要么我昨天就持有着股票,但是今天不sell,所以我今天还持有着股票;
//要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
最值 dp[len - 1][k_max][0]
即最后一天,最多允许k_max次交易,此时不持有股票,最多获得多少利润。
III题解
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if (len == 0)
{
return 0;
}
int k_max = 2;//k_max最大交易次数,k为至今最多交易次数
// 多维数组初始化
//vector<vector<vector<int>>> dp(len,vector<vector<int>>(k_max+1,vector<int>(2,0)));
int dp[len][k_max+1][2];
//初始状态分为两种,第一种是任何一天,交易次数至多0笔;第二种是第0天有至多1到k_max笔交易数.
for(int i = 0; i < len; i ++ ) dp[i][0][0] = 0, dp[i][0][1] = INT_MIN;
for(int k = 1; k <= k_max; k ++ ) dp[0][k][0] = 0, dp[0][k][1] = -prices[0];
for(int i = 1; i < len; i ++ ) {
for(int k = 1; k <= k_max; k++ )
{
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[len - 1][k_max][0];
}
};
188: IV 题目描述:
你最多可以完成k笔交易。
解答过程参见III中分析
IV题解
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len = prices.size();
if (len == 0)
{
return 0;
}
int k_max = k;//k_max最大交易次数,k为至今最多交易次数
// 多维数组初始化
//vector<vector<vector<int>>> dp(len,vector<vector<int>>(k_max+1,vector<int>(2,0)));
int dp[len][k_max+1][2];
//初始状态分为两种,第一种是任何一天,交易次数至多0笔;第二种是第0天有至多1到k_max笔交易数.
for(int i = 0; i < len; i ++ ) dp[i][0][0] = 0, dp[i][0][1] = INT_MIN;
for(int k = 1; k <= k_max; k ++ ) dp[0][k][0] = 0, dp[0][k][1] = -prices[0];
for(int i = 1; i < len; i ++ ) {
for(int k = 1; k <= k_max; k++ )
{
dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]);
dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]);
}
}
return dp[len - 1][k_max][0];
}
};
309. 最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
动态规划步骤
第1步:定义状态
状态dp[i][j]定义如下:
- 第一维 i 表示索引为 i 的那一天(具有前缀性质,即考虑了之前天数的收益)能获得的最大利润;
- 第二维 j 表示索引为 i 的那一天是持有股票,还是持有现金。这里 0 表示持有现金(cash),1 表示持有股票(stock)。
第2步:定义状态转移方程
第i天仅有两种状态:持有或者不持有.
- 持有状态dp[i][1]
- 今天买入:则前天(昨天的昨天)是不持有的状态,当天利益为dp[i-2][0]-prices[i];
- 前一天也持有:今天没有卖出,当天利益为dp[i-1][1];取其大者作为当天持有状态下的利益。
- 非持有状态dp[i][0]
- 今天卖出:则昨天是持有的状态,当天利益为dp[i-1][1]+prices[i];
- 前一天也不持有:则今天也没有买入,当天利益就是前一天利益,即dp[i-1][0];
取其大者作为当天非持有状态下的利益。
第3步:确定起始状态
- 如果什么都不做,dp[0][0] = 0;
- 如果买入股票,当前收益是负数,即 dp[0][1] = -prices[0];
第4步:确定终止
- 终止的时候,上面也分析了,输出 dp[len - 1][0],因为一定有 dp[len - 1][0] > dp[len - 1][1]。
包含冷冻期题解
class Solution {
public:
int maxProfit(vector<int>& prices) {
// 动态规划解法
// 0.初始判断
if(prices.empty()) return 0;
int n = prices.size();
if( n == 1) return 0;
int dp[prices.size()][2];
// 1.初始状态
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = max(dp[0][0],dp[0][1] + prices[1]);//由于i从2开始,初始值需要重新定义
dp[1][1] = max(dp[0][1],dp[0][0]-prices[1]);
// 2.状态转移
for(int i = 2; i < prices.size();i++){
// 3.状态转移方程
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[prices.size()-1][0];
}
};
714. 买卖股票的最佳时机含手续费
思路见II可以不限次数买入卖出或者冷冻期部分。
包含手续费题解
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
// 动态规划解法
// 0.初始判断
if(prices.empty()) return 0;
int n = prices.size();
if( n == 1) return 0;
int dp[prices.size()][2];
// 1.初始状态
dp[0][0] = 0;
dp[0][1] = -prices[0]; //如果买入时支付手续费,则初始买入时也应该正确赋值dp[0][1] = -prices[0] - fee;
// 2.状态转移
for(int i = 1; i < prices.size();i++){
// 3.状态转移方程
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]);//这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
}
return dp[prices.size()-1][0];
}
};
121. 买卖股票的最佳时机
股票问题(Python3、C++)
买卖股票的最佳时机 II
买卖股票的最佳时机II-贪心算法+DP
暴力搜索、贪心算法、动态规划
动态规划-最简洁的介绍状态转移
五种实现+详细图解 123.买卖股票的最佳时机 III
一个方法团灭 LeetCode 股票买卖问题