解法
解法LC上写得很详细了,大概概括一下吧。
主要有2类算法,分类的原则是如何表示循环数组的子串,一种是A*2,第二种是2区间
A*2
遇到这种循环题,我的第一反应就是把拼两个A,得到新数组,这样问题转化为新数组的最大子串和问题
最大子串和问题前面做过了,可以用两个前缀和相减来做:
但是由于一个元素只能在子串里出现一次,所以有一个附加条件:
由于变量有两个,肯定是固定j求最好的i。
就是说,A*2的方法中,循环数组的子串可以表示为
首先算出前缀和数组
,再遍历它,当遍历到
时,显然最优解是j前面长度为n的窗口的P的最小值(前面不足n个数的时候就当作窗口可以当作前面填了空元素)
是不是有点眼熟,滑动窗口最值,好像可以用队列来做:
class Solution(object):
def maxSubarraySumCircular(self, A):
"""
:type A: List[int]
:rtype: int
"""
n = len(A)
A += A[:]
P = [0]
cur = 0
for num in A:
cur += num
P.append(cur)
queue = [0]
ans = -30001
for i in xrange(1,2*n+1):
if i-queue[0]>n:
queue.pop(0)
ans = max(ans, P[i]-P[queue[0]])
while len(queue) and P[queue[-1]]>=P[i]:
queue.pop()
queue.append(i)
return ans
2-区间
2-区间主要是基于一个叫kadane算法的东西来做的
这个算法是用来求最大子串和的DP算法
状态转移方程是:
其中,
代表的是以
结尾的所有子串的最大和
它可以写成一个省空间的形式:
cur = -INF
ans = -INF
for num in A:
cur = max(cur,0)+a[j]
ans = max(ans, cur)
当数组A不是循环数组的时候,所有的子串可以表示成
但是当A是循环数组的时候,一部分子串可以这么表示,另一部分子串只能表示成:
。我们把前者叫1-区间,后者叫这就是所指的2-区间。
就是说,2-区间的方法中,循环数组的子串可以表示为
对于1-区间,kadane算法就可以求解了
对于2-区间,有两种求解算法:
- 设
为j往后的后缀和,固定i求最大的j:
- 通过求补集[i+1,j-1]的和就可以求出2-区间的子串和,因为整个数组的总和为常数,要使2-区间子串和最大,需要使补集和最小,所以整个问题转化成2个问题:求一遍1-区间最大子串和,再求一遍1-区间最小子串和(子串不可以为空,但不能为全集)。子串不能为空是因为为空的结果在1-区间里算过了,子串不能为全集就得拆成
A[1:]
和A[:-1]
个人比较喜欢第2种做法,代码如下:
class Solution(object):
def maxSubarraySumCircular(self, A):
"""
:type A: List[int]
:rtype: int
"""
n = len(A)
INF = 30000
cur_max, ans_max = -INF, -INF
cur_min1, ans_min1 = INF, INF
cur_min2, ans_min2 = INF, INF
for i,x in enumerate(A):
cur_max = max(cur_max,0)+x
ans_max = max(ans_max, cur_max)
if i>0:
cur_min1 = min(cur_min1,0)+x
ans_min1 = min(ans_min1, cur_min1)
if i<n-1:
cur_min2 = min(cur_min2, 0) + x
ans_min2 = min(ans_min2, cur_min2)
all = sum(A)
return max(ans_max, all-ans_min1, all-ans_min2)