小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
最近动态规划的题目慢慢开始有了点感觉,不过应该还有很多类型没接触到。道阻且长,继续求索。
题目
给定一个由整数数组 A 表示的环形数组 C,求 C 的非空子数组的最大可能和。
在此处,环形数组意味着数组的末端将会与开头相连呈环状。(形式上,当0 <= i < A.length 时 C[i] = A[i],且当 i >= 0 时 C[i+A.length] = C[i])
此外,子数组最多只能包含固定缓冲区 A 中的每个元素一次。(形式上,对于子数组 C[i], C[i+1], ..., C[j],不存在 i <= k1, k2 <= j 其中 k1 % A.length = k2 % A.length)
示例 1:
输入:[1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3
示例 2:
输入:[5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10
示例 3:
输入:[3,-1,2,-1]
输出:4
解释:从子数组 [2,-1,3] 得到最大和 2 + (-1) + 3 = 4
示例 4:
输入:[3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3
示例 5:
输入:[-2,-3,-1]
输出:-1
解释:从子数组 [-1] 得到最大和 -1
思路
本题其实也是最大子序列和的变形,很多动态规划的题目都是在基础的类型上演变而来,就想昨天的打家劫舍其实也是这样。 怎么求最大子序列和不讲了,讲讲变形的内容。 不同之处在于,可以有i<j的前提下,nums[j]~nums[len-1] 和 nums[0]~nums[i]的子序列。 那对结果的影响呢,最大子序列如果是正常的子序列,就用一般的方式来求解就好;如果对于两段式的子序列,可以反过来,用数组总和减去中间一个一般子序列,为了获得最大的和,其实就是求中间的最小子序列。最后,取 max 和 sum - min 中更大的值就好。 不过有一种特殊的情况,就是实例5,我们获取的最小子序列的和min,不能包含数组的全部元素,不然反过来剩下的子序列就是空了,想了一下有2种方式解决,最最小子序列和的时候,对nums[0]~nums[len-2]和nums[1]~nums[len-1]求2次,或者还有1种取巧的方式是,这种情况一定是所有的数字都是负数,这时候max一定也是负数(max这时会等于数组中最大的负数),所以此时最大子序列的和就是max,特殊处理掉这种情况下,剩下的就都是一般的情况了。
Java版本代码
class Solution {
public int maxSubarraySumCircular(int[] nums) {
// 设max是最大子串和,min是最小子串和
// 如果是正常数组,结果就是max,如果是循环数组,结果是sum-min
// 但是有一种特殊情况,就是min是数组中所有数字和的情况,这是后sum-min就不剩下任何数字了,构不成子串
// 这种情况,必然所有的元素都是负数,那么最大和其实还是max
int max = nums[0];
int maxpre = nums[0];
int min = nums[0];
int minpre = nums[0];
int sum = nums[0];
for (int i = 1; i < nums.length; i++) {
sum += nums[i];
int maxTemp = nums[i] + Math.max(maxpre, 0);
maxpre = maxTemp;
max = Math.max(max, maxTemp);
int minTemp = nums[i] + Math.min(minpre, 0);
minpre = minTemp;
min = Math.min(min, minTemp);
}
if (max < 0) {
return max;
}
return Math.max(max, sum - min);
}
}