剑指 Offer 42. 连续子数组的最大和

剑指 Offer 42. 连续子数组的最大和

题目大意

输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n)。

解题思路

  1. 滑动窗口(错误)
  2. 动态规划(虽然通常我们用递归的方式分析动态规划的问题,但最终都会基于循环去编码)
  3. 前缀和
  4. 如果没有想到用动态规划的思想,那么应聘者就需要仔细地分析累加子数组的和的过程,从而找到解题的规律

解法一: 举例分析数组的规律

例如计算数组{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

实现步骤

  1. 设置两个变量currentSum(记录当前最大和)和greatestSum(记录当前子数组的最大值), 分别初始化为0和负无穷
  2. 遍历数组nums, 若currentSum小于等于0, 则更新为遍历到的当前值, 否则加上当前值
  3. 每次遍历一个位置, 都会比较更新, 保证取值最大

如果第一个数换做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, 因此可以说这两 种思路是异曲同.

时间复杂度

  1. 暴力枚举对应的时间复杂度为\(n(n+1)/2\) (很容易想到, 所有的情况对应1~n的累加)
posted @ 2022-08-16 11:12  别把梦弄脏  阅读(60)  评论(0)    收藏  举报