文章目录
- 一、121、LeetCode买卖股票的最佳时机
- 1.1 动态规划
- 1.2 动态规划-滚动数组
- 二、122、买卖股票的最佳时机 II
- 三、123、买卖股票的最佳时机 III
- 七、完整代码
所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。
一、121、LeetCode买卖股票的最佳时机
1.1 动态规划
思路分析:
- 第一步,动态数组的含义。本题股票买卖只能进行一次,需要考虑再第 i i i天的时候要不要持有股票(注意持有股票未必是买入第 i i i天的股票,也有可能购买前面天数的股票。它是一种状态,不是一种动作),由此产生了持有和不持有的两种状态。因此我们根据天数和是否持有股票这两个维度创建二维动态数组 d p [ i ] [ j ] dp[i][j] dp[i][j]。 d p [ i ] [ j ] dp[i][j] dp[i][j]一共两列: d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表持有股票所得最多现金, d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表不持有股票所得的最多现金。其中, i i i代表天数。假设最初的现金为 0 0 0,那么加入第 i i i天买入股票现金就是 − p r i c e s [ i ] -prices[i] −prices[i](负数)。
- 第二步,递推公式。 d p [ i ] [ 0 ] dp[i][0] dp[i][0]可以由两种情况得出:第 i − 1 i-1 i−1天就持有股票了(只能买一次,之前买过),那么 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0] = dp[i-1][0] dp[i][0]=dp[i−1][0];第 i i i天买入股票,那么 d p [ i ] [ 0 ] = − p r i c e s [ i ] dp[i][0] = -prices[i] dp[i][0]=−prices[i]。因为要获取最大利润,那么只能挑便宜的买(这两个值都是负数),故我们在二者之间取最大值: d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , − p r i c e s [ i ] ) dp[i][0] = max(dp[i - 1][0], -prices[i]) dp[i][0]=max(dp[i−1][0],−prices[i])。同理, d p [ i ] [ 1 ] dp[i][1] dp[i][1]也可以由两种情况得出:第 i − 1 i-1 i−1天不持有股票(已经卖出或者还未买入),那么保持现状 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] dp[i][1] = dp[i - 1][1] dp[i][1]=dp[i−1][1];第 i i i天卖出股票,有 d p [ i ] [ 1 ] = p r i c e s [ i ] + d p [ i − 1 ] [ 0 ] dp[i][1] = prices[i] + dp[i - 1][0] dp[i][1]=prices[i]+dp[i−1][0]。因为要挑贵的卖出,故取最大值: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , p r i c e s [ i ] + d p [ i − 1 ] [ 0 ] ) dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]) dp[i][1]=max(dp[i−1][1],prices[i]+dp[i−1][0])。
- 第三部,元素初始化。令 d p [ 0 ] [ 0 ] = − p r i c e s [ 0 ] dp[0][0] = -prices[0] dp[0][0]=−prices[0], d p [ 0 ] [ 1 ] = 0 dp[0][1] = 0 dp[0][1]=0。
- 第四部,递归顺序。循环从 i = 1 i = 1 i=1开始。
- 第五步,打印结果。最终必然是要卖出股票,状态是不持有股票,所以返回 d p [ p r i c e s . s i z e ( ) − 1 ] [ 1 ] dp[prices.size() - 1][1] dp[prices.size()−1][1]。
程序如下:
// 121、股票I-动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = max(dp[i - 1][0], -prices[i]); // 第i天持有股票
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]); // 第i天不持有股票
}
return dp[prices.size() - 1][1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
1.2 动态规划-滚动数组
在上面程序的基础之上,我们发现对于
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]来说,只利用了
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j],因此空间不必开辟那么大。利用滚动数组,我们将空间复杂度降低到
O
(
1
)
O(1)
O(1)。
程序如下:
// 121、股票I-动态规划-滚动数组
class Solution2 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]); // 第i天持有股票
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]); // 第i天不持有股票
}
return dp[(prices.size() - 1) % 2][1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
二、122、买卖股票的最佳时机 II
思路分析:本题与121题不同之处在于可以多次买卖,但是买入一只股票之前不能持有其他股票。因为这一点不一样,所以当第
i
i
i天买入股票的时候,所持有的现金可能有之前买卖过的利润。那么第
i
i
i天持有股票即
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0],如果是第
i
i
i天买入股票,所得现金就是昨天不持有股票的所得现金减去今天的股票价格 即:
d
p
[
i
−
1
]
[
1
]
−
p
r
i
c
e
s
[
i
]
dp[i - 1][1] - prices[i]
dp[i−1][1]−prices[i]。其余部分的分析和121题完全一样。
程序如下:
// 122、股票II-动态规划
class Solution3 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 第i天持有股票
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]); // 第i天不持有股票
}
return dp[prices.size() - 1][1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
同理,本题也可以用滚动数组降低空间复杂度:
// 122、股票II-动态规划-滚动数组
class Solution4 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]); // 第i天持有股票
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]); // 第i天不持有股票
}
return dp[(prices.size() - 1) % 2][1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
三、123、买卖股票的最佳时机 III
思路分析:本题又是另外一个变体,最多可有完成两笔交易,同时买入一只股票之前不能持有其他股票。一天一共只有五个状态:1、没有操作;2、第一次持有股票;3、第一次不持有股票;4、第二次持有股票;5、第二次不持有股票。套用121题的思路,我们创建一个二维数组,行代表天数,列代表状态。
七、完整代码
# include <iostream>
# include <vector>
using namespace std;
// 121、股票I-动态规划
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = max(dp[i - 1][0], -prices[i]); // 第i天持有股票
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]); // 第i天不持有股票
}
return dp[prices.size() - 1][1];
}
};
// 121、股票I-动态规划-滚动数组
class Solution2 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]); // 第i天持有股票
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]); // 第i天不持有股票
}
return dp[(prices.size() - 1) % 2][1];
}
};
// 122、股票II-动态规划
class Solution3 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 第i天持有股票
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]); // 第i天不持有股票
}
return dp[prices.size() - 1][1];
}
};
// 122、股票II-动态规划-滚动数组
class Solution4 {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2)); // 二维数组,行代表天数,列代表是否持有股票
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]); // 第i天持有股票
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]); // 第i天不持有股票
}
return dp[(prices.size() - 1) % 2][1];
}
};
int main() {
vector<int> prices = { 7,1,5,3,6,4 };
Solution s1;
int result = s1.maxProfit(prices);
cout << result << endl;
system("pause");
return 0;
}
end