剑指 Offer 42. 连续子数组的最大和
题目大意
输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n)。
解题思路
- 滑动窗口(错误)
- 动态规划(虽然通常我们用递归的方式分析动态规划的问题,但最终都会基于循环去编码)
- 前缀和
- 如果没有想到用动态规划的思想,那么应聘者就需要仔细地分析累加子数组的和的过程,从而找到解题的规律
解法一: 举例分析数组的规律
例如计算数组{1, -2, 3, 10, -4, 7, 2, -5}中子数组的最大和的过程中, 我们试着从头到尾逐个累加示例数组中的每个数字, 初始化和为0, 累加计算步骤如下表所示:
| 步骤 | 操作 | 累加的子数组和 | 最大的子数组和 |
|---|---|---|---|
| 1 | 加1 | 1 | 1 |
| 2 | 加-2 | -1 | 1 |
| 3 | 抛弃前面的和-1, 加3 | 3 | 3 |
| 4 | 加10 | 13 | 13 |
| 5 | 加-4 | 9 | 13 |
| 6 | 加7 | 16 | 16 |
| 7 | 加2 | 18 | 18 |
| 8 | 加-5 | 13 | 18 |
实现步骤
- 设置两个变量
currentSum(记录当前最大和)和greatestSum(记录当前子数组的最大值), 分别初始化为0和负无穷 - 遍历数组
nums, 若currentSum小于等于0, 则更新为遍历到的当前值, 否则加上当前值 - 每次遍历一个位置, 都会比较更新, 保证取值最大
如果第一个数换做100, 第二个数换做-101, 最大和就是100, 遍历到第三个数的时候, 因为前两个的和为-1, 因此currentSum要从第三个数重新开始累加, 但是greatestSum始终保持100不变.
Python实现
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
currentSum, greatestSum = 0, -float('inf')
for i in range(len(nums)):
if currentSum <= 0:
currentSum = nums[i]
else:
currentSum += nums[i]
if currentSum > greatestSum:
greatestSum = max(currentSum, greatestSum)
return greatestSum
解法2: 应用动态规划
如果算法的功底足够扎实,我们还可以用动态规划的思想来分析这个问题。如果用函数\(f(i)\)表示以第 \(i\)个数字结尾的子数组的最大和,那么我们需要求出 \(max[f(i)]\),其中\(i<=0<n\)。我们可用如下边归公式求\(f(i)\):
\[f(i)= \begin{cases}p \operatorname{Data}[i] & i=0 \text { 或者 } f(i-1) \leq 0 \\ f(i-1)+p D a t a[i] & i \neq 0 \text { 并且 } f(i-1)>0\end{cases}
\]
这个公式的意义:当以第 \(i-1\) 个数字结尾的子数组中所有数字的和小于\(0\)时,如果把这个负数与第 \(i\) 个数累加,得到的结果比第\(i\)个数字本身还要小,所以这种情况下以第 \(i\)个数字结尾的子数组就是第 \(i\)个数字本身. 如果以第\(i-1\)个数字结尾的子数组中所有数字的和大于 \(0\), 与第 \(i\)个数字累加就得到以第 \(i\)个数字结尾的子数组中所有数字的和。
虽然通常我们用递归的方式分析动态规划的问题,但最终都会基于循 环去编码。上述公式对应的代码和前面给出的代码一致。递归公式中的 \(f(i)\) 对应的变量是 CurrentSum,而 max[f(i)]就是 greatestSum, 因此可以说这两 种思路是异曲同.
时间复杂度
暴力枚举对应的时间复杂度为\(n(n+1)/2\) (很容易想到, 所有的情况对应1~n的累加)

浙公网安备 33010602011771号