• LeetCode 0053. 最大子数组和:DP 或 递归(线段树入门题?)


    【LetMeFly】53.最大子数组和:DP 或 递归

    力扣题目链接:https://leetcode.cn/problems/maximum-subarray/

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    子数组 是数组中的一个连续部分。

     

    示例 1:

    输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
    输出:6
    解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
    

    示例 2:

    输入:nums = [1]
    输出:1
    

    示例 3:

    输入:nums = [5,4,-1,7,8]
    输出:23
    

     

    提示:

    • 1 <= nums.length <= 105
    • -104 <= nums[i] <= 104

     

    进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

    方法一:DP

    使用动态规划的话思路比较简单,使用一个变量 c n t cnt cnt记录以当前元素为结尾的最大子数组和

    这样,我们只需要遍历一遍 n u m s nums nums数组,使用公式 c n t = max ⁡ ( c n t + n u m s [ i ] , n u m s [ i ] ) cnt = \max(cnt + nums[i], nums[i]) cnt=max(cnt+nums[i],nums[i])维护 c n t cnt cnt,并记得更新答案的最大值即可。

    • 时间复杂度 O ( l e n ( n u m s ) ) O(len(nums)) O(len(nums))
    • 空间复杂度 O ( 1 ) O(1) O(1)

    AC代码

    C++
    class Solution {
    public:
        int maxSubArray(vector<int>& nums) {
            int ans = nums[0];
            int cnt = nums[0];
            for (int i = 1; i < nums.size(); i++) {
                cnt = max(cnt + nums[i], nums[i]);
                ans = max(ans, cnt);
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    Python
    # from typing import List
    
    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            ans, cnt = nums[0], nums[0]
            for i in range(1, len(nums)):
                cnt = max(cnt + nums[i], nums[i])
                ans = max(ans, cnt)
            return ans
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    方法二:递归(分治)

    写一个函数 g e t ( n u m s , l , r ) get(nums, l, r) get(nums,l,r),返回 n u m s nums nums数组从 l l l r r r的子数组的:

    1. lSum: 以 n u m s [ l ] nums[l] nums[l]为起点的最大子数组和
    2. rSum: 以 n u m s [ r ] nums[r] nums[r]为终点的最大子数组和
    3. MSum: 最大子数组和
    4. iSum: 和

    那么,我们就可以愉快地进行递归啦!

    对于 g e t ( n u m s , l , r ) get(nums, l, r) get(nums,l,r),我们可以分别求出 g e t ( n u m s , l , ⌊ l + r 2 ⌋ ) get(nums, l, \lfloor\frac{l + r}{2}\rfloor) get(nums,l,2l+r⌋)(记为 l S t a t u s lStatus lStatus)和 g e t ( n u m s , ⌊ l + r 2 ⌋ + 1 , r ) get(nums, \lfloor\frac{l + r}{2}\rfloor + 1, r) get(nums,2l+r+1,r)(记为 r S t a t u s rStatus rStatus)。递归终止条件为 l = r l=r l=r(只有单个元素)。

    于是就有:

    1. l S u m = max ⁡ ( l S t a t u s . l S u m , l S t a t u s . i S u m + r S t a t u s . l S u m ) lSum = \max(lStatus.lSum, lStatus.iSum + rStatus.lSum) lSum=max(lStatus.lSum,lStatus.iSum+rStatus.lSum)(以 n u m s [ l ] nums[l] nums[l]为起点,不跨过 n u m s [ ⌊ l + r 2 ⌋ ] nums[\lfloor\frac{l + r}{2}\rfloor] nums[⌊2l+r⌋]和跨过)
    2. r S u m = max ⁡ ( r S t a t u s . r S u m , l S t a t u s . r S u m + r S t a t u s . i S u m ) rSum = \max(rStatus.rSum, lStatus.rSum + rStatus.iSum) rSum=max(rStatus.rSum,lStatus.rSum+rStatus.iSum)(以 n u m s [ r ] nums[r] nums[r]为终点,不跨过 n u m s [ ⌊ l + r 2 ⌋ ] nums[\lfloor\frac{l + r}{2}\rfloor] nums[⌊2l+r⌋]和跨过)
    3. M S u m = max ⁡ ( l S t a t u s . M S u m , r S t a t u s . M S u m , l S t a t u s . r S u m + r S t a t u s . l S u m ) MSum = \max(lStatus.MSum, rStatus.MSum, lStatus.rSum + rStatus.lSum) MSum=max(lStatus.MSum,rStatus.MSum,lStatus.rSum+rStatus.lSum)(左半部分最大子数组和、右半部分最大子数组和、跨过 n u m s [ ⌊ l + r 2 ⌋ ] nums[\lfloor\frac{l + r}{2}\rfloor] nums[⌊2l+r⌋]的子数组和)
    4. i S u m = l S t a t u s . i S u m + r S t a t u s . i S u m iSum = lStatus.iSum + rStatus.iSum iSum=lStatus.iSum+rStatus.iSum(左半右半数组和 之和)

    最终返回 g e t ( n u m s , 0 , l e n ( n u m s ) − 1 ) . M S u m get(nums, 0, len(nums) - 1).MSum get(nums,0,len(nums)1).MSum即可。

    • 时间复杂度 O ( l e n ( n u m s ) ) O(len(nums)) O(len(nums))(相当于后序遍历了一遍二叉树)
    • 空间复杂度 O ( log ⁡ l e n ( n u m s ) ) O(\log len(nums)) O(loglen(nums))(空间复杂度主要来源于递归)

    AC代码

    C++
    struct Status {
        int lSum, rSum, MSum, iSum;
    };
    
    class Solution {
    private:
        Status get(vector<int>& a, int l, int r) {  // get[l, r]
            if (l == r) {
                return {a[l], a[l], a[l], a[l]};
            }
            int m = (l + r) >> 1;
            Status lStatus = get(a, l, m);
            Status rStatus = get(a, m + 1, r);
            return {
                max(lStatus.lSum, lStatus.iSum + rStatus.lSum),
                max(rStatus.rSum, lStatus.rSum + rStatus.iSum),
                max(lStatus.MSum, max(rStatus.MSum, lStatus.rSum + rStatus.lSum)),
                lStatus.iSum + rStatus.iSum
            };
        }
    public:
        int maxSubArray(vector<int>& nums) {
            return get(nums, 0, nums.size() - 1).MSum;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    Python
    # from typing import List
    
    
    class Status:
        def __init__(self, lSum: int, rSum: int, MSum: int, iSum: int) -> None:
            self.lSum = lSum
            self.rSum = rSum
            self.MSum = MSum
            self.iSum = iSum
    
    
    class Solution:
        def get(self, nums: List[int], l: int, r: int) -> Status:
            if l == r:
                return Status(nums[l], nums[l], nums[l], nums[l])
            m = (l + r) >> 1
            lStatus = self.get(nums, l, m)
            rStatus = self.get(nums, m + 1, r)
            return Status(
                max(lStatus.lSum, lStatus.iSum + rStatus.lSum),
                max(rStatus.rSum, lStatus.rSum + rStatus.iSum),
                max(lStatus.MSum, rStatus.MSum, lStatus.rSum + rStatus.lSum),
                lStatus.iSum + rStatus.iSum
            )
        
        def maxSubArray(self, nums: List[int]) -> int:
            return self.get(nums, 0, len(nums) - 1).MSum
    
    
    """为何不用切片作为参数?
    >>> a = [1, 2, 3]
    >>> a
    [1, 2, 3]
    >>> b = a[1:2]
    >>> b
    [2]
    >>> b[0] = 99
    >>> a
    [1, 2, 3]
    >>> b
    [99]
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    方法二意义何在?

    相较于方法一,方法二的时间复杂度没有提升,空间复杂度反而更高了。那么方法二的意义何在?

    这道题只问了“整个数组的”最大子数组和。但是如果某天遇到了一道题,问你 1 0 5 10^5 105次且每次随机问一个 [ l , r ] [l, r] [l,r]的最大子数组和 呢?

    那么我们使用方法二,并且将每层的结果记录下来,就能做到每次查询都在 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度下返回结果。

    这就是没有懒标记的线段树。

    同步发文于CSDN,原创不易,转载经作者同意后请附上原文链接哦~
    Tisfy:https://letmefly.blog.csdn.net/article/details/134504375

  • 相关阅读:
    Candies 差分约束
    微信小程序和H5之间互相跳转、互相传值
    【面向小白】深究模型大小和推理速度的关系!
    基于GNS3的某省农科院网络组网规划方案设计
    Leetcode《图解数据结构》刷题日志【第三周】(2022/10/31-2022/11/06)
    零零信安-D&D数据泄露报警日报【第28期】
    盘一盘那些高性能设计的点(一)
    访问者模式
    Day15--加入购物车实现加入购物车的功能
    【11】c++11新特性 —>move移动语义(2)
  • 原文地址:https://blog.csdn.net/Tisfy/article/details/134504375