53. (Maximum Subarray)最大子序和

Posted OIqng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了53. (Maximum Subarray)最大子序和相关的知识,希望对你有一定的参考价值。

题目:

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

A subarray is a contiguous part of an array.

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

Example 1:

Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

示例 1:

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

Example 2:

Input: nums = [1]
Output: 1

示例 2:

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

Example 3:

Input: nums = [5,4,-1,7,8]
Output: 23

示例 3:

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

Constraints:

  • 1 <= nums.length <= 3 * 1 0 4 10^4 104
  • - 1 0 5 10^5 105 <= nums[i] <= 1 0 5 10^5 105

提示:

  • 1 <= nums.length <= 3 * 1 0 4 10^4 104
  • - 1 0 5 10^5 105 <= nums[i] <= 1 0 5 10^5 105

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

进阶:

如果您已经找到了O(n)解决方案,请尝试使用分治方法编写另一个解决方案,看看哪个更微妙。

解题思路:

方法一:贪心算法

贪心算法:做出当前最优解,即某种意义上的局部最优解。

就本题来说:求最大子序和来说当数组nums =[-2,1,-3,4,-1,2,1,-5,4]计算子序和的时候一定是从1开始(即前一个子序和丢弃),而当子序和小于0时则放弃当前子序和从下一个元素开始计算。

python代码

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        pre = -float('inf')
        subMax = 0
        for i in range(len(nums)):
            subMax += nums[i]
            if subMax > pre:
                pre = subMax
            if subMax <= 0:
                subMax = 0
        return pre

Java代码

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }
        int pre = Integer.MIN_VALUE;
        int subMax = 0;
        for (int i = 0; i < nums.length; i++){
            subMax += nums[i];
            pre = Math.max(pre, subMax);
            if (subMax <= 0){
                subMax = 0; 
            }
        }
       return pre;
    }
}

C++代码

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = INT_MIN;
        int subMax = 0;
        for (int i = 0; i < nums.size(); i++) {
            subMax += nums[i];
            if (subMax > pre) { 
                pre = subMax;
            }
            if (subMax <= 0) { // 贪心的核心
                subMax = 0; 
            }
        }
        return pre;
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n为数组nums的长度。
  • 空间复杂度:O(1),只需常数空间存放变量。

方法二:动态规划

假设 nums 数组的长度是 n,下标从 0 到 n-1。

我们用 a(i) 代表以第 i 个数结尾的「连续子数组的最大和」,即用求最大值公式表达为:
a ( i ) = m a x a ( i ) 0 ≤ i ≤ n − 1 a(i)=max {a(i)}_{0≤i≤n−1} a(i)=maxa(i)0in1

我们需求出每个位置上的a(i),然后比较前一子序和是否大于0,当大于0时则当前子序和加上前一个子序和。从而我们需要比较 n u m s [ i ] n u m s ( i − 1 ) {nums}[i]nums(i−1) nums[i]nums(i1)
n u m s [ i ] nums[i] nums[i]的大小,于是得到以下的动态规划转移方程:

a ( i ) = m a x { n u m s ( i − 1 ) + n u m s [ i ] , n u m s [ i ] } a(i)=max\\{{nums(i−1)+nums[i],nums[i]}\\} a(i)=max{nums(i1)+nums[i],nums[i]}

python代码

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        subMax = 0
        pre = nums[0]
        for x in nums:
            subMax = max(subMax + x, x)
            pre = max(pre, subMax)
        return pre

Java代码

class Solution {
    public int maxSubArray(int[] nums) {
        int subMax = 0;
        int pre = nums[0];
        for (int x : nums){
            subMax = Math.max(subMax + x, x);
            pre = Math.max(pre, subMax);
        }
        return pre;
    }
}   

C++代码

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, subMax = nums[0]; 
        for (const auto &x: nums) {
            pre = max(pre + x, x);
            subMax = max(subMax, pre);
        }
        return maxAns;
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n为数组nums的长度。
  • 空间复杂度:O(1),只需常数空间存放变量。

方法三:分治

我们现在需求区间[l, r]上的最大连续子序和mSum,按照分治算法的思路,应当通过划分[l, r]为左区间[l, m]和右区间R:[m+ 1, r]分治求解。

在一个区间 [l,r]内我们使用以下四个:

  • lSum 表示 [l,r]内以 l 为左端点的最大子序和
  • rSum 表示 [l,r] 内以 r 为右端点的最大子序和
  • mSum 表示 [l,r] 内的最大子序和
  • iSum 表示 [l,r]的区间和

如何通过左右子区间的信息合并得到 [l,r]的信息?

  • 区间 [l,r]的iSum 就等于左子区间的iSum +右子区间的 iSum
  • [l,r]的lSum,存在两种可能,它要么等于左子区间的lSum,要么等于左子区间的 iSum +右子区间的 lSum,二者取大。
  • [l,r]的rSum,同理,它要么等于右子区间的 }rSum,要么等于右子区间的 iSum 加上左子区间的 rSum,二者取大。
  • [l,r] 的 mSum 可能是左子区间的 mSum 也可能是 右子区间mSum还可能是左子区间的 rSum 和右子区间的lSum 的和,三者取大。

python代码

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        max_num = self.findMax(nums)
        return max_num
    def findMax(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        else:
            max_left = self.findMax(nums[0:len(nums) // 2])
            max_right = self.findMax(nums[len(nums) // 2:len(nums)])
        max_l = nums[len(nums) // 2 - 1]
        count = 0
        for i in range(len(nums) // 2 - 1, -1, -1):
            count += nums[i]
            max_l = max(count, max_l)
        max_r = nums[len(nums) // 2]
        count = 0
        for i in range(len(nums) // 2, len(nums)):
            count += nums[i]
            max_r = max(count, max_r)
        return max(max_right, max_left, max_l + max_r)

Java代码

class Solution {
    public class Status {
        public int lSum, rSum, mSum, iSum;

        public Status(int lSum, int rSum, int mSum, int iSum) {
            this.lSum = lSum;
            this.rSum = rSum;
            this.mSum = mSum;
            this.iSum = iSum;
        }
    }

    public int maxSubArray(int[] nums) {
        return getInfo(nums, 0, nums.length - 1).mSum;
    }

    public Status getInfo(int[] a, int l, int r) {
        if (l == r) {
            return new Status(a[l], a[l], a[l], a[l]);
        }
        int m = (l + r) >> 1;
        Status lSub = getInfo(a, l, m);
        Status rSub = getInfo(a, m + 1, r);
        return pushUp(lSub, rSub);
    }

    public Status pushUp(Status l, Status r) {
        int iSum = l.iSum + r.iSum;
        int lSum = Math.max(l.lSum, l.iSum + r.lSum);
        int rSum = Math.max(r.rSum, r.iSum + l.rSum);
        int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);
        return new Status(lSum, rSum, mSum, iSum);
    }
}

C++代码

class Solution {
public:
    struct Status {
        int lSum, rSum, mSum, iSum;
    };

    Status pushUp(Status l, Status r) {
        int iSum = l.iSum + r.iSum;
        int lSum = max(l.lSum, l.iSum + r.lSum);
        int rSum = max(r.rSum, r.iSum + l.rSum);
        int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);
        return (Status) {lSum, rSum, mSum, iSum};
    };

    Status get(vector<int> &a, int l, int r) {
        if (l == r) {
            return (Status) {a[l], a[l], a[l], a[l]};
        }
        int m = (l + r) >> 1;
        Status lSub = get(a, l, m);
        Status rSub = get(a, m + 1, r);
        return pushUp(lSub, rSub);
    }

    int maxSubArray(vector<int>& nums) {
        return get(nums, 0, nums.size() - 1).mSum;
    }
};

复杂度分析

  • 时间复杂度:O( n l o g n nlogn nlogn),这里递归的深度是对数级别的,每一层需要遍历一遍数组。
  • 空间复杂度:O( l o g n logn logn),需常数个变量,使用的空间取决于递归栈的深度。

以上是关于53. (Maximum Subarray)最大子序和的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 53. Maximum Subarray 最大子数组和(中等)

53. Maximum Subarray(动态规划 求最大子数组)

[leetcode]53. Maximum Subarray最大子数组和

leetCode 53.Maximum Subarray (子数组的最大和) 解题思路方法

LeetCode 53. Maximum Subarray(最大的子数组)

leetcode53. 最大子序和(Maximum Subarray)