题目传送门:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
买卖股票
题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 =
6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。示例 2:
输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
推荐解法:保存最小值
寻找最大利润时,当卖出价固定时,买入价越低,获得的利润最大。因此,遍历价格时,保存当前最小值。
- 代码:
C++代码:
int MaxDiff(const int* numbers, unsigned length)
{
if(numbers == nullptr || length < 2)
return 0;
int min = numbers[0]; // 股票最低价
int maxDiff = numbers[1] - min; // 最大利润
for(int i = 2; i < length; i++)
{
if(numbers[i-1] < min) // 检查前一天是否是最小值,当前是出售股票
min = numbers[i-1];
int currDiff = numbers[i] - min;
if(currDiff > maxDiff)
maxDiff = currDiff;
}
return maxDiff;
}
解法一:利用股票左高右低/左低右高的趋势
- 思路:
仅利用相邻两天股票趋势(左高右低、左低右高)分析。获得最大收益的买入时间(谷底)肯定是左低右高的左侧值,卖出时间肯定是左高右低的左侧值或是左低右高的右侧值。这种思路画图即可理解。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int min = 0xfffffff;
int bigger = 0;
int maxDif = 0;
// 从前后开始查找谷底和峰值
for(int i = 0; i < len-1; i++)
{
// LeetCode判题需要先判断是否越界。然后才可访问。
// 左低右高
if(prices[i] < prices[i+1])
{
// 寻找目前为止的最佳买入时间
min = (prices[i] > min)? min : prices[i];
bigger = prices[i+1];
// maxDif = (prices[i+1] - min > maxDif)? (prices[i+1] - min) : maxDif;
}
else
{
bigger = prices[i];
// maxDif = (prices[i] - min > maxDif)? (prices[i] - min) : maxDif;
}
maxDif = (bigger - min > maxDif)? (bigger - min) : maxDif;
}
return maxDif;
}
};
解法二:利用谷底和峰值(先谷底再峰值)
这是官方解法!
- 思路:
使我们感兴趣的点是上股票趋势的峰和谷。我们需要找到最小的谷之后的最大的峰。 我们可以维持两个变量——minprice 和 maxprofit,它们分别对应迄今为止所得到的最小的谷值和最大的利润(卖出价格与最低价格之间的最大差值)。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int minprice = 0xfffffff;
int maxprofit = 0;
for (int i = 0; i < len; i++) {
if (prices[i] < minprice)
minprice = prices[i];
else if (prices[i] - minprice > maxprofit)
maxprofit = prices[i] - minprice;
}
return maxprofit;
}
}
复杂度分析
时间复杂度:O(n),只需要遍历一次。
空间复杂度:O(1),只使用了两个变量。
作者:LeetCode
链接:https://leetcode-cn.com/problems/two-sum/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-by-leetcode/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法三:动态规划(通用!解决所有股票问题)推荐!
思路链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
利用动态规划解决6道股票问题。动态规划(状态,选择,状态方程,初始状态)
显然 i = 0 时 dp[i-1] 是不合法的。这是因为我们没有对 i 的 base case 进行处理。可以这样处理:
for (int i = 0; i < n; i++) {
if (i - 1 == -1) {
dp[i][0] = 0;
// 解释:
// dp[i][0]
// = max(dp[-1][0], dp[-1][1] + prices[i])
// = max(0, -infinity + prices[i]) = 0
dp[i][1] = -prices[i];
//解释:
// dp[i][1]
// = max(dp[-1][1], dp[-1][0] - prices[i])
// = max(-infinity, 0 - prices[i])
// = -prices[i]
continue;
}
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
}
return dp[n - 1][0];
状态方程只需两个变量,可改进空间复杂度。新状态只和相邻的一个状态有关,其实不用整个 dp 数组,只需要一个变量储存相邻的那个状态就足够了,这样可以把空间复杂度降到 O(1)。如下:
// k == 1
int maxProfit_k_1(int[] prices) {
int n = prices.length;
// base case: dp[-1][0] = 0, dp[-1][1] = -infinity
int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
// dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
// dp[i][1] = max(dp[i-1][1], -prices[i])
dp_i_1 = Math.max(dp_i_1, -prices[i]);
}
return dp_i_0;
}
作者:labuladong
链接:https://leetcode-cn.com/problems/two-sum/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题目扩展:交易数K可取任意值
题目传动门:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/submissions/
- 思路:
动态规划算法。交易数K与天数的关系:一次交易由买入和卖出构成,至少需要两天。所以说有效的限制 k 应该不超过 n/2,如果超过,就没有约束作用了,相当于 k = +infinity。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if (k > n / 2)
{
int dp_i_0 = 0, dp_i_1 = INT_MIN;
for(int i = 0; i < n; i++)
{
int temp = dp_i_0;
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, temp - prices[i]);
}
return dp_i_0;
}
// dp三个状态分别为(天数,交易数,是否已买入)
vector<vector<vector<int> > > dp(n+1, vector<vector<int> >(k+1, vector<int>(2)));
for(int i = 0; i <= n; i++)
{
for(int j = k; j >= 1; j--)
{
if(i - 1 == -1)
{
dp[i][j][0] = 0;
dp[i][j][1] = INT_MIN;
continue;
}
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i-1]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i-1]);
}
}
return dp[n][k][0];
}
};
解法二:利用C++(pair模板)提高效率
class Solution{
public:
int maxProfit(int k, vector<int>& prices)
{
int len = prices.size();
if(len <= 1) return 0;
//std::vector<std::pair<int, int>> dp(k+1, {-prices[0], 0});
// 这里有一个隐藏很深的 bug, 就如果 k 的值很大, 就会直接把栈爆掉!!
// 所以应该按照 k 值做优化, 将 vector 声明在 if 语句内部
if(k < len / 2)
{
// 利用pair模板方便存储。first:当前已买股票;second:当前未买股票
vector<pair<int, int> > dp(k+1, {-prices[0], 0});
for (int i = 1; i < len; i++)
{
for (int j =1; j < k + 1; j++)
{
// hold(拥有股票) 存储当前已买股票与上次未买这次购买两者中的最大利润
int hold = std::max(dp[j].first, dp[j-1].second - prices[i]);
// not_hold(未拥有股票) 存储当前保持无股票与上次拥有这次卖出两者中的最大利润
int not_hold = max(dp[j].second, dp[j].first + prices[i]);
dp[j].first = hold;
dp[j].second = not_hold;
}
}
return dp[k].second;
}
else
{
pair<int, int> dp = {-prices[0], 0};
for(int i = 1; i < prices.size(); i++)
{
int hold = std::max(dp.first, dp.second - prices[i]);
int not_hold = std::max(dp.second, dp.first + prices[i]);
dp.first = hold;
dp.second = not_hold;
}
return dp.second;
}
}
};
Python版
Python 实现:
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if len(prices) <= 1: return 0
if (k < len(prices) // 2) :
dp = [[-prices[0], 0] for i in range(k+1)]
for price in prices[1:]:
for i in range(1, k+1):
dp[i] = [max(dp[i][0], dp[i-1][1]-price), max(dp[i][1], dp[i][0]+price)]
return dp[k][1]
else:
dp = [-prices[0], 0]
for price in prices[1:]:
dp = [max(dp[0], dp[1]-price), max(dp[1], dp[0]+price)]
return dp[1]
作者:hellozhaozheng
链接:https://leetcode-cn.com/problems/two-sum/solution/tong-yong-fang-fa-de-chao-jian-ji-shi-xian-fu-che-/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。