LeetCode122之股票买卖的最好时机(相关话题:动态规划,记忆搜索,状态机,贪心算法)

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

解题思路

方法1:

start代表买入点,初始值为0,写出递推式:maxProfit2(prices, start)= prices[buy]  - prices[sell] + maxProfit2(prices, sell+1)

   /**
    * 表示start处售出的最大收益
    */
 
   public static int maxProfit2(int[] prices, int start) {

		
		int res = 0;
		if (start >= prices.length) {
			return 0;
		}

		if(prices.length==0){
			return 0;
		}

		// 引入记忆搜索。提高效率
		if (mem[start] != 0) {
			return mem[start];
		}

		for (int buy = 0; buy < prices.length; buy++) {
			for (int sell = buy + 1; sell < prices.length; sell++) {
				res = Math.max(res, prices[sell] - prices[buy] + maxProfit2(prices, sell + 1));
			}
		}

		mem[start] = res;
		return res;
	}

 方法2:

    如果反过来想,固定卖出时间 sell,向前穷举买入时间 buy,寻找 prices[buy] 最小的那天,是不是也能达到相同的效果?是的,而且这种思路可以减少一个 for 循环。

	public static int maxProfit3(int[] prices, int start) {

		int res = 0;
		if (start >= prices.length) {
			return 0;
		}
		if (mem[start] != 0) {
			return mem[start];
		}

		int minPrice = prices[start];

		for (int sell = start; sell < prices.length; sell++) {

			minPrice = Math.min(minPrice, prices[sell]);
			res = Math.max(res, maxProfit3(prices, sell + 1) + prices[sell] - minPrice);
		}

		mem[start] = res;
		return res;
	}

 上面两种方法提交到LeecCode上都超时了,所以下面重点介绍状态机的解法

方法3:

考虑到「不能同时参与多笔交易」,因此每天交易结束后只可能存在手里有一支股票或者没有股票的状态。

定义状态dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。

考虑 dp[i][0] 的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即dp[i−1][0],或者前一天结束的时候手里持有一支股票,即dp[i−1][1],这时候我们要将其卖出,并获得 prices[i] 的收益。因此为了收益最大化,我们列出如下的转移方程:

dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]}

再来考虑 dp[i][1],按照同样的方式考虑转移状态,那么可能的转移状态为前一天已经持有一支股票,即dp[i−1][1],或者前一天结束时还没有股票,即dp[i−1][0],这时候我们要将其买入,并减少prices[i] 的收益。可以列出如下的转移方程:

dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}

对于初始状态,根据状态定义我们可以知道第 0 天交易结束的时候

dp[0][0]=0,  dp[0][1]=−prices[0]。

因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持有股票的收益,因此这时候dp[n−1][0] 的收益必然是大于 dp[n−1][1] 的,最后的答案即为 dp[n−1][0]。

public  int maxProfit(int[] prices) {

		int n = prices.length;
		int dp[][] = new int[n][2];
		for (int i = 0; i < n; i++) {
			if (i - 1 == -1) {
				dp[i][0] = 0;
				dp[i][1] = -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], dp[i - 1][0] - prices[i]);
		}

		return dp[n - 1][0];

}

方法4:

方法4是对方法3的优化变形

	public static int maxProfit2(int[] prices) {

		int n = prices.length;
		int dp_i_0 = 0, dp_i_1 = -Integer.MAX_VALUE;
		// 且注意一下状态转移方程,新状态只和相邻的一个状态有关,
		// 其实不用整个 dp 数组,只需要两个变量储存所需的状态就足够了,这样可以把空间复杂度降到 O(1):
		for (int i = 0; i < n; i++) {

			int tmp = dp_i_0;
			dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
			dp_i_1 = Math.max(dp_i_1, tmp - prices[i]);
		}
		return dp_i_0;

	}

方法5:

贪心算法特点:一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的。

这道题,我们知道如果后面的数,比前面的数大,那么就可以进行买卖,此时利润profit>0的,可以累加。

class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for (int i = 1; i < prices.length; i++) {
            int tmp = prices[i] - prices[i - 1];
            if (tmp > 0) profit += tmp;
        }
        return profit;
    }
}

参考文章

LeetCode 股票问题的一种通用解法

团灭 LeetCode 股票买卖问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值