1.1最大子数组和
给一个整数数组 nums ,找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
。
输入
:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出
:6
解释
:连续子数组 [4,-1,2,1] 的和最大,为 6 。
动态规划
动态规划也称为动态优化
,把复杂问题分解为子问题,通过求解子问题,组合子问题的解从而得到整个问题的解。参考文章
动态规划适用于:
1.一个复杂问题的最优解由数个小问题的最优解构成
,可以通过寻找子问题的最优解来得到复杂问题的最优解,这个特性又称为最优子结构(optimal substructure);例如:最短路径
2.子问题在复杂问题内重复出现,使得子问题的解可以被存储起来重复利用
,这个特性又称为重叠子问题(overlapping subproblem)。例如:斐波拉契数列。
动态规划的无后效性
为了保证计算子问题能够按照顺序、不重复地进行,动态规划要求已经求解的子问题不受后续阶段的影响。这个条件也被叫做「无后效性
」。换言之,动态规划对状态空间的遍历构成一张有向无环图,遍历就是该有向无环图的一个拓扑序
。有向无环图中的节点对应问题中的「状态
」,图中的边则对应状态之间的「转移
」,转移的选取就是动态规划中的「决策
」。
动态规划题解的步骤
:状态定义、状态转移方程、初始化、输出、是否可以空间优化。
1.定义子问题:
子问题 1:以 −2
结尾的连续子数组的最大和是多少;
子问题 2:以 1
结尾的连续子数组的最大和是多少;
子问题 3:以 −3
结尾的连续子数组的最大和是多少;
子问题 4:以 4
结尾的连续子数组的最大和是多少;
子问题 5:以 −1
结尾的连续子数组的最大和是多少;
子问题 6:以 2
结尾的连续子数组的最大和是多少;
子问题 7:以 1
结尾的连续子数组的最大和是多少;
子问题 8:以 −5
结尾的连续子数组的最大和是多少;
子问题 9:以 4
结尾的连续子数组的最大和是多少。
2.定义状态
d p [ i ] dp[i] dp[i]:表示以 n u m s [ i ] nums[i] nums[i]结尾 的 连续 子数组的最大和。
3.状态转移方程(子问题之间的联系)
以 n u m s [ i ] nums[i] nums[i]结尾的连续子数组与以 n u m s [ i − 1 ] nums[i - 1] nums[i−1] 结尾的连续子数组只相差一个元素 n u m s [ i ] nums[i] nums[i] 。
假设数组 n u m s nums nums 的值全都严格大于 0,那么一定有 d p [ i ] = d p [ i − 1 ] + n u m s [ i ] dp[i] = dp[i - 1] + nums[i] dp[i]=dp[i−1]+nums[i]。
可是
d
p
[
i
−
1
]
dp[i - 1]
dp[i−1]有可能是负数,于是分类讨论:
如果
d
p
[
i
−
1
]
>
0
dp[i - 1] > 0
dp[i−1]>0,那么可以把
n
u
m
s
[
i
]
nums[i]
nums[i] 直接接在
d
p
[
i
−
1
]
dp[i - 1]
dp[i−1] 表示的那个数组的后面,得到和更大的连续子数组;
如果
d
p
[
i
−
1
]
<
=
0
dp[i - 1] <= 0
dp[i−1]<=0,那么
n
u
m
s
[
i
]
nums[i]
nums[i] 加上前面的数
d
p
[
i
−
1
]
dp[i - 1]
dp[i−1] 以后值不会变大。于是
d
p
[
i
]
dp[i]
dp[i] 「另起炉灶」,此时单独的一个
n
u
m
s
[
i
]
nums[i]
nums[i] 的值,就是
d
p
[
i
]
dp[i]
dp[i]。
以上两种情况的最大值就是
d
p
[
i
]
dp[i]
dp[i]的值,写出如下状态转移方程:
d
p
[
i
]
=
{
d
p
[
i
−
1
]
+
n
u
m
s
[
i
]
if
d
p
[
i
−
1
]
>
0
n
u
m
s
[
i
]
if
d
p
[
i
−
1
]
<
=
0
dp[i]= \begin{cases} dp[i-1]+nums[i] &\text{if } dp[i-1]>0\\ nums[i] &\text{if } dp[i-1]<=0 \end{cases}
dp[i]={dp[i−1]+nums[i]nums[i]if dp[i−1]>0if dp[i−1]<=0
4.初始化
d p [ 0 ] dp[0] dp[0] 根据定义,只有 1 个数,一定以 n u m s [ 0 ] nums[0] nums[0]结尾,因此 d p [ 0 ] = n u m s [ 0 ] dp[0] = nums[0] dp[0]=nums[0]。
5.输出
这个问题的输出是把所有的 d p [ 0 ] 、 d p [ 1 ] 、 … … 、 d p [ n − 1 ] dp[0]、dp[1]、……、dp[n - 1] dp[0]、dp[1]、……、dp[n−1]都看一遍,取最大值。
6.是否可以优化空间
一般的问题只要时间复杂度最优就可以。
空间复杂度
o
n
l
i
n
e
j
u
d
g
e
online judge
onlinejudge并不在意,只要使用的空间不太离谱,不要一上来就
i
n
t
[
]
d
p
=
n
e
w
i
n
t
[
I
n
t
e
g
e
r
.
M
A
X
V
A
L
U
E
]
int[] dp = new int[Integer.MAX_VALUE]
int[]dp=newint[Integer.MAXVALUE]就好。
7.代码
class question1:
def maxSubArray(self,nums):
size=len(nums)
pre=0 #初始状态
res=nums[0] #存储每个状态中的最大值
for i in range(size):
pre=max(nums[i],pre+nums[i]) #计算以nums[i]结尾的最大值
res=max(res,pre)
return res
q = question1()
print(q.maxSubArray([-2,1,-3,4,-1,2,1,-5,4]))
1.2任意子数组和的绝对值的最大值
数组 [ n 1 , n 2 , n 3 , . . . , n r − 1 , n r ] 的和的绝对值为 a b s ( n 1 + n 2 + n 3 + . . . + n r − 1 + n r ) 数组[n_1,n_2,n_3,...,n_{r-1},n_r]的 和的绝对值 为abs(n_1+n_2+n_3+...+n_{r-1}+n_r) 数组[n1,n2,n3,...,nr−1,nr]的和的绝对值为abs(n1+n2+n3+...+nr−1+nr)
其中abs()
为取绝对值,定义如下:
1.如果 x 是负整数,那么
a
b
s
(
x
)
=
−
x
abs(x) = -x
abs(x)=−x 。
2.如果 x 是非负整数,那么
a
b
s
(
x
)
=
x
abs(x) = x
abs(x)=x 。
请你找出 nums 中 和的绝对值
最大的任意子数组(可能为空),并返回该最大值 。子数组
是数组中的一个连续非空
序列。
输入
:nums = [2,-5,1,-4,3,-2]
输出
:8
解释
:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8 。
nums 中 和的绝对值
最大的任意子数组可能是这个数组的子数组最大和的绝对值,也可能是这个数组的子数组最小和的绝对值,按照1.1的动态规划思路
,求取数组的子数组最大和和子数组最小和。
class question1:
def maxAbsoluteSum(self,nums):
preMax,preMin=0,0
resMax,resMin=nums[0],nums[0]
for i in range(len(nums)):
preMax=max(nums[i],nums[i]+preMax)
resMax=max(resMax,preMax)
preMin = min(nums[i], nums[i] + preMin)
resMin = min(preMin, resMin)
return max(abs(resMax),abs(-resMin))
q = question1()
print(q.maxAbsoluteSum([2,-5,1,-4,3,-2]))