给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
- 思路:涉及到买入 / 卖出,同一天手头金额会有变化,统一用 进行完买卖选择之后 的状态
- dp[i] 表示第 i 天 结算后 的最大金额👉能否买入 / 卖出 依赖于前一天的状态(是否持有股票)👉dp[i][0]表示第 i 天 结算后 持有股票,dp[i][1]表示第 i 天 结算后 不持有股票
- 递推公式:
- dp[i][0]表示第 i 天 结算后 持有股票的最大金额,有两个来源:
- 第 i - 1 天 结算后 就持有股票,第 i 天不卖出,即 dp[i - 1][0]
- 第 i - 1 天 结算后 不持有股票,而第 i 天买入,即 dp[i - 1][1] - prices[i]
- 注:由于只能买入一次,如果第 i 天买入,那么 dp[i - 1][1] 一定是0,即 -prices[i]
- dp[i][0] = max(dp[i - 1][0], -prices[i])
- dp[i][1]表示第 i 天 结算后 不持有股票的最大金额,有两个来源:
- 第 i - 1 天 结算后 不持有股票,第 i 天也不买入,即 dp[i - 1][1]
- 第 i - 1 天 结算后 持有股票,但第 i 天将其卖出,即 dp[i - 1][0] + prices[i]
- dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i])
- dp[i][0]表示第 i 天 结算后 持有股票的最大金额,有两个来源:
- 初始化:
- 第 0 天 结算后 持有股票,即买入股票prices[0]👉dp[0][0] = -prices[0]
- 第 0 天 结算后 不持有股票,即不买入股票prices[0]👉dp[0][1] = 0
- 遍历顺序:从前向后
- ⭐最终结果在dp[prices.size() - 1][1]:因为 dp[prices.size() - 1][0] 一定 <=0(相当于股票砸手里,还不如不买,不买是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 == 0) return 0;
vector<vector<int>> dp(len, vector<int>(2));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
}
return dp[len - 1][1];
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)
// 动态规划:版本二(滚动数组)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
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[(len - 1) % 2][1];
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)
// 自己:最多买一次、卖一次,故dp0不依赖于前一天的dp1,可先更新dp1(要依赖于前一天的dp0),后更新dp0
class Solution {
public:
int maxProfit(vector<int>& prices) {
int dp0 = -prices[0]; // 持有股票
int dp1 = 0; // 不持有股票
for (int i = 1; i < prices.size(); ++i) {
dp1 = max(dp1, dp0 + prices[i]); // 买入一定在卖出之前
dp0 = max(dp0, -prices[i]); // 要买就买最便宜的那支
}
return dp1;
}
};
LeetCdoe 122.买卖股票的最佳时机II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
视频讲解https://www.bilibili.com/video/BV1D24y1Q7Ls文章讲解https://programmercarl.com/0122.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAII%EF%BC%88%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%EF%BC%89.html
- 思路:题中说“可以先购买,然后在同一天出售”,不管先买后卖还是先卖后买,从 结算后 持有股票状态 和 金额 来看相当于 没买没卖。
- dp数组含义:
- dp[i][0] 表示 第i天 结算后 持有股票 所得最大金额
- dp[i][1] 表示 第i天 结算后 不持有股票 所得最大金额
- 递推公式:
- dp[i][0]:第i天 结算后 持有股票
- 第i - 1天结算后就持有股票
- 第i - 1天结算后不持有股票,以 prices[i] 买入
- dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i])
- 注:由于可以买入多次,如果第 i 天买入,那么 dp[i - 1][1]不一定是0
- dp[i][1]:第i天 结算后 不持有股票
- 第i - 1天结算后就不持有股票
- 第i - 1天结算后持有股票,以 prices[i] 卖出
- dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0])
- dp[i][0]:第i天 结算后 持有股票
- 初始化:dp[0][0] = -prices[0],其余值全0
- 遍历顺序:从前向后
- ⭐最终结果在dp[prices.size() - 1][1]:因为 dp[prices.size() - 1][0] 一定 <= dp[x][1] <= dp[prices.size() - 1][1](相当于股票砸手里,还不如不买这支股票)
- dp数组含义:
- 代码:
// 动态规划:版本一
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[len - 1][1];
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)
// 动态规划:版本二(滚动数组)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)
// 自己:dp0依赖于前一天的dp0, dp1而dp1依赖于前一天的dp0, dp1
class Solution {
public:
int maxProfit(vector<int>& prices) {
int dp0 = -prices[0]; // 持有股票
int dp1 = 0; // 不持有股票
int tmp = 0; // 记录前一天的dp1,用于dp0的计算
for (int i = 1; i < prices.size(); ++i) {
dp1 = max(dp1, dp0 + prices[i]);
dp0 = max(dp0, tmp - prices[i]);
tmp = dp1; //用于下一天dp0的计算
}
return max(dp0, dp1);
}
};
思考:这道题确实贪心解法简洁很多,但贪心需要具体问题具体分析,而动态规划整体框架固定,解决股票全家桶也不在话下。