【LeetCode】121. 买卖股票的最佳时机

题目传送门: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) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值