DP-求子数列最大和/积-MaxEndHere

这篇博客总结了利用动态规划(DP)解决LeetCode中的53. Maximum Subarray, 121. Best Time to Buy and Sell Stock, 和152. Maximum Product Subarray问题。作者指出,虽然表面看似不同,但实际上都是寻找子序列的最大值,可以通过DP避免重复计算。对于最大和问题,DP状态表示以下标i结尾的子数组的最大值,并通过比较前一状态与当前元素的和来更新。对于股票交易问题,求解的是累积收益的最大值。而对于最大乘积问题,需要跟踪最大和最小两个值,以便在负数出现时翻盘。" 99064557,8738408,Python与JMeter、Locust生成不重复数据策略,"['造数据', '性能测试', 'Python编程', 'JMeter工具', '负载测试']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是我写的第一篇leetcode的数据结构算法练习的总结。各个论坛上有很多总结leetcode的解法的博客,但很多是按照作者的理解分类的。这里我按照我自己的思路总结,通过写可以梳理一遍,让自己思路更清晰。
有些题,表面看上去不一样,仔细想想,就会觉得,“脱了马甲我还认得你!”

今天是DP动态规划问题的一个子类,就是在某个点,若之前的累积结果已经不能再产生积极的contribution的情况下,要把之前的舍弃。
53和121很类似。

题目简介
53. Maximum Subarray求子数列最大和
121. Best Time to Buy and Sell Stock买股票-求子数列最大和
152. Maximum Product Subarray求子数列最大积

53. Maximum Subarray

求子数列的和的最大值
Input: [-2,1,-3,4,-1,2,1,-5,4], Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

直觉:假想我们有个sliding window,我们只需要确定了左右边界,就可以唯一确定这个subarray,然后对每个window求和,然后维护一个max即可。
为啥不能这么做?
因为这样O(n * n)循环左右边界,O(n)求和,总共就O(n^3),太复杂了。
而且,关键在于这样的重复计算特别多!DP可以用来避免重复计算。
并且,次题目标是“最大值”,并不是要求“所有可能的组合”,于是就很容易想到用DP。
这个题为啥不是一个sliding window的问题呢?
因为sliding window问题先拓展右边界,然后根据“满足条件”或“不满足条件”来收缩左边界。题目要求要么固定窗长度求sum最大/小,要么给定sum求窗最大/小。这里既不给定窗长度,也不给定sum,于是不存在“满足条件”。所以不是sliding window问题。

dp[i]定义:以下标i结尾的subarray的最大值(maxEndHere)
在下标i处:

  1. 如果前面的累积结果(上一个maxEndHere,相当于dp[i-1])小于零,则此处加上这个累积结果,还不如不加,于是就不加了。
    即: maxEndHere = nums[i]

  2. 如果累积结果大于零,则加上比较好
    即:maxEndHere = nums[i] + maxEndHere(上一个)

class Solution {
    public int maxSubArray(int[] nums) {
        int len = nums.length;
        int maxEndHere = 0, maxSoFar = Integer.MIN_VALUE;
        for (int i = 0; i < len; i++) {
            maxEndHere = Math.max(maxEndHere+nums[i], nums[i]);
            maxSoFar = Math.max(maxSoFar, maxEndHere);
        }
        return maxSoFar;
    }
}

121. Best Time to Buy and Sell Stock

Input: [7,1,5,3,6,4], Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. Not 7-1 = 6, as selling price needs to be larger than buying price.

这个121和刚才53的联系:
如果53求累计和:
A = [-2, 1, -3,4,-1,2,1,-5,4] (每个值)
B = [-2,-1,-4, 0, -1,1,2, -3, 1] (从头开始的累积值)
53题就是求某点i 值和之前的某点j 之间的差的最大值。
同理,把121还原:
A = [7,1,5,3,6,4] (累积值)
B = [7,-6,4,-2,3,-2] (昨天买今天卖的收益,delta)
我们要求的是:max subarray of B
总结一下:求subarray比较容易,于是都划归为subarray问题。
prices[i] - prices[i-1] 就是delta
类似地,求两者之中的最大值:

  1. 不抛弃之前的,maxEndHere + prices[i] - prices[i-1]
  2. 抛弃之前的,prices[i] - prices[i-1]
class Solution {
    public int maxProfit(int[] prices) {
        int maxSoFar = 0;
        int maxEndHere = 0;
        for (int i = 1; i < prices.length; i++) {
            maxEndHere = Math.max(prices[i] - prices[i-1], maxEndHere + prices[i] - prices[i-1]);
            maxSoFar = Math.max(maxSoFar, maxEndHere);
        }
        return maxSoFar;
    }
}

152. Maximum Product Subarray

Input: [2,3,-2,4] Output: 6
Explanation: [2,3] has the largest product 6.

152是53的拓展,53是求最大和,152是求最大积。
区别在于:

  • 53求和,一旦发现之前的累积和是负数,就可以断定它对后面不会有contribution了。
  • 然而152求积就不一样了,即使是负数,后面负负得正还可以翻盘。

于是我们怎么处理呢?发现了一个规律,即使负负得正的情况,也不是随便一个值就可以达到最终的正值是最大值的,需要那个负值是负值里的最小的(即绝对值最大),于是我们track最大和最小两个值。
if (nums[i] < 0) 时候的那个翻转的原因

  1. maxEndHere本来是用来辅助行成可能的最大值的candidate,当nums[i]<0的时候,只有负数(或者更小的正数)才能更好地实现这个目标,于是我们应该使用minEndHere。
  2. minEndHere同理。所以两者要交换。
class Solution {
    public int maxProduct(int[] nums) {
        int result = nums[0]; //维护一个最大值
        for (int i = 1, maxEndHere = result, minEndHere = result; i < nums.length; i++) {
            //若乘上负数,会把max和min翻转了
            if (nums[i] < 0) {
                int temp = maxEndHere;
                maxEndHere = minEndHere;
                minEndHere = temp;
            }
            //和之前53,121类似,维护可能的max和min
            maxEndHere = Math.max(nums[i], maxEndHere * nums[i]);
            minEndHere = Math.min(nums[i], minEndHere * nums[i]);
            //维护最大值
            result = Math.max(result, maxEndHere);
        }
        return result;
    }
}

今天先写到这里吧哈哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值