这是我写的第一篇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处:
-
如果前面的累积结果(上一个maxEndHere,相当于dp[i-1])小于零,则此处加上这个累积结果,还不如不加,于是就不加了。
即: maxEndHere = nums[i] -
如果累积结果大于零,则加上比较好
即: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
类似地,求两者之中的最大值:
- 不抛弃之前的,maxEndHere + prices[i] - prices[i-1]
- 抛弃之前的,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) 时候的那个翻转的原因:
- maxEndHere本来是用来辅助行成可能的最大值的candidate,当nums[i]<0的时候,只有负数(或者更小的正数)才能更好地实现这个目标,于是我们应该使用minEndHere。
- 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;
}
}
今天先写到这里吧哈哈。