解法
解法LC上写得很详细了,大概概括一下吧。
主要有2类算法,分类的原则是如何表示循环数组的子串,一种是A*2,第二种是2区间
A*2
遇到这种循环题,我的第一反应就是把拼两个A,得到新数组,这样问题转化为新数组的最大子串和问题
最大子串和问题前面做过了,可以用两个前缀和相减来做:
a
n
s
[
j
]
=
max
i
,
j
(
P
[
j
]
−
P
[
i
]
)
ans[j] = \max_{i,j}(P[j]-P[i])
ans[j]=i,jmax(P[j]−P[i])
但是由于一个元素只能在子串里出现一次,所以有一个附加条件:
j
−
i
<
=
n
j-i<=n
j−i<=n
由于变量有两个,肯定是固定j求最好的i。
就是说,A*2的方法中,循环数组的子串可以表示为 [ i , j ] , 0 ≤ i , j < 2 n 且 j − i ≤ n [i,j],0\le i,j<2n\ 且\ j-i\le n [i,j],0≤i,j<2n 且 j−i≤n
首先算出前缀和数组
P
P
P,再遍历它,当遍历到
P
[
j
]
P[j]
P[j]时,显然最优解是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算法
状态转移方程是:
d
p
[
j
]
=
max
(
d
p
[
j
−
1
]
,
0
)
+
a
[
j
]
dp[j] = \max(dp[j-1], 0) + a[j]
dp[j]=max(dp[j−1],0)+a[j]
其中,
d
p
[
j
]
dp[j]
dp[j]代表的是以
a
[
j
]
a[j]
a[j]结尾的所有子串的最大和
它可以写成一个省空间的形式:
cur = -INF
ans = -INF
for num in A:
cur = max(cur,0)+a[j]
ans = max(ans, cur)
当数组A不是循环数组的时候,所有的子串可以表示成
[
i
,
j
]
[i,j]
[i,j]
但是当A是循环数组的时候,一部分子串可以这么表示,另一部分子串只能表示成:
[
j
,
n
−
1
]
+
[
0
,
i
]
[j,n-1]+[0,i]
[j,n−1]+[0,i]。我们把前者叫1-区间,后者叫这就是所指的2-区间。
就是说,2-区间的方法中,循环数组的子串可以表示为
[ i , j ] , 0 ≤ i ≤ j < n [ 0 , i ] , [ j , n − 1 ] , 0 ≤ i < j < n \begin{matrix} [i,j]&, 0\le i\le j<n\\ [0,i],[j,n-1]&, 0\le i<j<n \end{matrix} [i,j][0,i],[j,n−1],0≤i≤j<n,0≤i<j<n
对于1-区间,kadane算法就可以求解了
对于2-区间,有两种求解算法:
- 设
R
j
R_j
Rj为j往后的后缀和,固定i求最大的j:
a n s [ i ] = P [ i ] + max j > i R j ans[i] = P[i]+\max_{j>i}R_j ans[i]=P[i]+j>imaxRj - 通过求补集[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)