【动态规划】 LeetCode #121 买卖股票的最佳时机(可不创建数组)

题目链接:

LeetCode #121 买卖股票的最佳时机

题目描述:

买卖股票的最佳时机
给定一个数组,它的第 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。
通过次数199,984 提交次数370,510

解决方案:

先从题目出发进行理解,如果是第 i 天卖出股票,那在这之前必须有 < prices[i] 的,想要得到最大利润,要找出前 i-1 天中小于 prices[i] 的最小值。显然,如果对每一天都去找之前比它便宜的最小值,会有很多重复计算。

而动态规划会把已经计算过的结果保存下来,避免重复计算,且该题符合动态规划的条件。

设 dp[i] 为第 i 天卖出获得的最大利润。

每天的价格与前一天对比,只会有三种情况:

如果当天价格和前一天相同,即 prices[i] = prices[i-1],那么第 i 天卖出能拿到的最大利润和前一天卖出相同,有 dp[i] = dp[i-1];

如果当天价格高于前一天,由于 dp[i-1] 代表第 i-1 天卖出的最大利润,那么第 i 天卖出的最大利润就等于前一天卖出的最大利润加上两天的价格差,即 dp[i] = prices[i] - prices[i-1] + dp[i-1];

如果当天价格低于前一天,就要考虑两种情况第一种,虽然利润少,但还是有利润,即 dp[i-1] > prices[i-1] - prices[i],那么 dp[i] = dp[i-1] - (prices[i-1] - prices[i])第二种,前面所有的价格都比当天的要高,没有利润,根据题意这种情况就不买,也就是利润为 0,即 dp[i] = 0(可以理解为,前一天的最大利润比这两天的价格差值还要小,dp[i-1] - (prices[i-1] - prices[i] < 0,就不卖出了)。

代码:

在这里插入图片描述

/*
*动态规划:设dp[i]为第i天卖出获得的最大利润
*/
class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length < 2) return 0;//空数组或者只有一个元素,没有利润
        int[] dp = new int[prices.length];
        dp[0] = 0;//第一天卖出最高利润为0
        int result = 0;
        for(int i = 1; i < prices.length; i++){
            if(prices[i] == prices[i-1]){
                dp[i] = dp[i-1];
            }else if(prices[i] > prices[i-1]){
                dp[i] = prices[i] - prices[i-1] + dp[i-1];
                if(result < dp[i]){ result = dp[i];}
            }else{
                if(dp[i-1] > prices[i-1] - prices[i]){
                    dp[i] = dp[i-1] - (prices[i-1] - prices[i]); 
                }
            }
        }
        return result;
    }
}

针对空间复杂度进行优化

动态规划在空间复杂度上的优化思想大同小异,我们每次计算 dp[i] 都只用到了前面的一项,所以可以不创建新的数组,用一个变量暂存上一个数据即可。

/*
*动态规划:设dp[i]为第i天卖出获得的最大利润
*优化:每次计算 dp[i] 其实只用到了其前一位,可以不创建数组
*/
class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length < 2) return 0;//空数组或者只有一个元素,没有利润
        int dp = 0;
        
        int result = 0;
        for(int i = 1; i < prices.length; i++){
            int prev = dp;
            if(prices[i] > prices[i-1]){
                dp = prices[i] - prices[i-1] + dp;
                if(result < dp){ result = dp;}
            }else{
                if(dp > prices[i-1] - prices[i]){
                    dp = dp - (prices[i-1] - prices[i]); 
                }else{ dp = 0;}
            }
        }
        return result;
    }
}

提交结果如下:
在这里插入图片描述
我们其实没有改变时间复杂度,只是把空间复杂度降低了,为什么会得到这样的执行结果呢?

首先,关于时间复杂度,由于本身用时很短,只有1~2ms,这个时候即使时间复杂度相同,你访问的次数多点儿少点儿都可能对结果产生很大的影响,可能一个 dp = a + b 和 dp = dp + a + b 的时间差就能让总的执行时间发生改变。

然后是空间复杂度,理论上我们把空间复杂度从 O(n) 降到了 O(1),但是LeetCode上的内存消耗不只是你的程序开辟的空间,测试用例也是占内存的,以这道题为例,通过了 200 个测试用例,每个测试用例都是一个数组,我们对空间的优化只产生了 0.x MB 的差距,个人觉得把空间复杂度优化到 O(1) 就足够了,不用过于在意内存消耗击败了多少人…

最后是关于动态规划思想的一点儿感悟,动态规划思想更像是用空间去换时间,我们不想去重复计算已经计算过的数据,那就把这个数据保存下来,这可能也是很多题目采用动态规划思想时,执行用时往往能击败多数用户的原因。再就是,既然我们想避免重复的运算,在思考问题的时候就要多去想想,怎么才能在计算时用上之前的计算,也就是推导状态转移方程式的过程。最后,推导过程中一定要注意把各种情况全覆盖,比如本题分为了:prices[i] = prices[i-1];prices[i] > prices[i-1];prices[i] < prices[i-1],这已经是这两者关系的全部可能性了,如果你的想法很难去覆盖所有的情况,可以尝试换个思路,路就能走宽了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值