121. Best Time to Buy and Sell Stock
目标是找到max(prices[j]−prices[i]), for every i and j such that j > i.
直接用两层循环可能会超时。
更好的方法是遍历一遍prices[]
数组,每一次遍历执行一下两个操作:
- 记录下目前为止遇到的最小值
- 计算当前
price
与最小值的差值,与已经得到的前面部分的最大值作比较,取较大者。
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 1 || n == 0) return 0;
vector<int> res(n, 0);
int min = prices[0];
int final_res = 0;
for (int i = 1; i < n; ++i) {
if (prices[i] < min) min = prices[i];
if (prices[i] - min > final_res) {
final_res = prices[i] - min;
}
}
return final_res;
}
};
122. Best Time to Buy and Sell Stock II
与上一道题同样,是一道简单题,但是直接用两层循环可能会超时。
可以对prices[]
进行遍历,找到每一段升序子段,结果加上首尾差值即可。
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 1 || n == 0) return 0;
int cur_max, cur_min;
cur_max = cur_min = prices[0];
int final_res = 0;
for (int i = 1; i < n; ++i) {
if (prices[i] > prices[i - 1]) {
cur_max = prices[i];
} else {
final_res += cur_max - cur_min;
cur_min = cur_max = prices[i];
}
}
final_res += prices[n - 1] - cur_min;
return final_res;
}
};
更简洁的方法是:对每一个prices[i]
,如果prices[i] - prices[i-1] > 0
,则结果加上该差值。只要后一个数比前一个数大,就加上两数的差值,效果与以上方法是一样的。
123. Best Time to Buy and Sell Stock III
初始收益profit = 0
,购买时profit -= prices[i]
,出售时profit += prices[i]
。
这里有四个变量:
- buy1:表示只买了一块石头的收益
- sell1:表示买一块并卖了一块石头的收益
- buy2:表示买了第二块石头时的收益,根据题目,此时肯定已经买卖过一次石头了
- sell2:表示卖了第二块石头时的收益
对prices[]
进行遍历,每一次遍历都计算出以上4个值,则最后的结果必定是max(sell1, sell2)
。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() <= 1) return 0;
int buy1 = INT_MIN, buy2 = INT_MIN;
int sell1 = 0, sell2 = 0;
for (int i = 0; i < prices.size(); ++i) {
sell2 = max(sell2, buy2 + prices[i]);
buy2 = max(buy2, sell1 - prices[i]);
sell1 = max(sell1, buy1 + prices[i]);
buy1 = max(buy1, -prices[i]);
}
return sell2 < 0 ? 0 : sell2;
}
};
以上代码的一些解释:
Q: 为何计算顺序是sell2
、buy2
、sell1
、buy1
?
A: 因为每一个值都是根据上一步计算的结果推出来的。以sell2
为例,它是根据上一步的buy2
推导出来的,若是buy2
先于sell2
计算,则在计算sell2
时,所使用的buy2
的值是这一步的结果,而不是上一步的结果。其他变量类似。
Q: 为什么直接返回sell2
?
A: sell2
是根据buy2
计算的,buy2
是根据sell1
计算的,这会导致sell2
始终比sell1
大。
PS:以上思路来自LeetCode的Disscussion。
188. Best Time to Buy and Sell Stock IV
用动态规划的思路来写。
将买卖一次石头称作一次事务,只有执行至少一次事务时才会有收益。
dp[i][j]
表示:prices[]
从 0 到 j-1 个元素并且执行了 i 个事务时的最大收益。
首先确定边界:
dp[i][0] = 0 : 第一个元素的地方收益当然为0
dp[0][j] = 0 : 一次事务都没执行,收益当然为0
现在考虑一般情况:
dp[i][j]
表示到prices[j-1]
并且执行了j
次事务时的最大收益,该值来源于两个方面:
dp[i][j-1]
:到prices[j-1]
(即第j天)时,事务数量没有增加(没有卖出石头,收益也就不会增加)max( prices[j] - prices[jj] + dp[i-1, jj] {0<=jj<j} )
: 到prices[j-1]
(即第j天)时,事务数量增加1
以上第二个值的解释:prices[jj]
之前(0到jj-1)视为一个执行了i-1次事务的子段,dp[i-1][jj]
为此时的最大收益,该值加上jj+1到j之间的最大差值,就可能是dp[i][j]
。但是这个jj可能会有多个值,因此这些prices[j] - prices[jj] + dp[i-1, jj] {0<=jj<j}
中的最大值即是dp[i][j]
的两个可能来源之一。
而dp[i][j]
就是以上两个值之中的最大值:
p[i][j] = max(dp[i][j-1], max( prices[j] - prices[jj] + dp[i-1, jj]))
= max(dp[i, j-1], prices[j] + max(dp[i-1, jj] - prices[jj]));
另外,记n为数组长度,则最多有n/2个事务(每个事务至少涉及两个元素),
因此当k>n/2时,该题跟122题是一样的。
代码如下:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if (prices.size() <= 1) return 0;
int n = prices.size();
if (k > n / 2) return quick_solve(prices);
vector<vector<int> > dp(k + 1, vector<int>(n, 0));
for (int i = 1; i <= k; ++i) {
int tmp_max = -prices[0];
for (int j = 1; j < n; ++j) {
dp[i][j] = max(dp[i][j - 1], tmp_max + prices[j]);
tmp_max = max(tmp_max, dp[i - 1][j] - prices[j]);
}
}
return dp[k][n - 1];
}
int quick_solve(vector<int>& prices) {
int n = prices.size();
int res = 0;
for (int i = 1; i < n; ++i) {
if (prices[i] > prices[i - 1])
res += prices[i] - prices[i - 1];
}
return res;
}
};