【LeetCode】918.环形子数组的最大和
地址
https://leetcode-cn.com/problems/maximum-sum-circular-subarray/
描述
给定一个由整数数组 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)
思路
首先先思考一个简化问题:对一个非环形数组,我们如何求解它的非空子数组的最大可能和?
很显然,这个是动态规划类题里很经典的一个问题 : 53. 最大子序和。我们可以通过动态规划的思想,记录位置i结尾的子序列的最大和为 dp[i],那么第i个元素结尾的最大和可以表示为:dp[i] = num[i] + max(0, dp[i-1])。(原理是位置i的最大和,只可能是 num[i] 和 num[i] + dp[i-1]的其中一个,如果dp[i-1]<0,则一定是num[i])。随后通过循环累计就可以求出每个位置的最大和,每次求出来比较下即可。
而本问题是上述问题的一个变种,它特殊就特殊在加了环形数组这个条件之后,当数组横跨了数组的起始点之后,计算dp变的复杂了。所以可以拆解成两个问题:
1) 计算区间就在当前数组本身内的最大可能和。这个简单,就和上面提到的问题53的解决思路一样。
2) 计算区间横跨了数组的起始点的情况。这个我们可以变通一下:环本身的总和 A 是固定的,求这种情况的最大值,可以转化为求,数组本身区间内的最小值,然后用 A - 这个最小值就可以了。
综上,根据分析,我们可以得出解决思路了:按照题53的求解方式,去遍历数组,在遍历过程中,求每个元素结尾的 最大子序列和 sumMax 和 最小子序列和 sumMin 。然后每一轮求 max(sumMax, A-sumMin) 去比较即可
要考虑一个特殊情况:那就是数组为单调负数的情况时,有可能会出现 A-sumMin = 0 的情况,这种情况是需要排除掉的(因为这是情况2的求解方式,但是A=sumMin的情况下,这个子序列就是数组本身,并不符合横跨的情况,需要排除)。
代码
func maxSubarraySumCircular(A []int) int {
//1.数组长度<=1时直接给出结果
if len(A) < 1 {
return 0
}
if len(A) == 1 {
return A[0]
}
//2.从第一个元素开始往后做推算
sum, sumMax, sumMin, prevMax, prevMin := A[0], A[0], A[0], A[0], A[0]
for i:=1; i<len(A); i++ {
//循环中顺便求和,最后结果时候会用到
sum += A[i]
//当前方程状态只与前一个元素相关
prevMax = A[i] + max(prevMax, 0)
prevMin = A[i] + min(prevMin, 0)
sumMax = max(sumMax, prevMax)
sumMin = min(sumMin, prevMin)
}
//3.排除极端情况的影响()
if sum == sumMin {
return sumMax
}
return max(sumMax, sum-sumMin)
}
func max (a, b int) int {
if a > b {
return a
}
return b
}
func min (a, b int) int {
if a < b {
return a
}
return b
}

浙公网安备 33010602011771号