LeetCode-53-最大子序和

LeetCode-53-最大子序和


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

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


示例:

示例 1:

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

解题思路:
Alice:这道题目也很熟悉啊。
Bob: 对啊,大二的算法课讲动态规划的话基本上一定会讲这个例子的。
Alice: 动态规划 ? Dynamic planning ? ?
Bob: 对啊,我们先来看看题目吧。求一个连续子数组的最大和,我们可以暴力求解。
Alice: 那样的话,要写一个双层循环了,外面的 i0数组末尾, 里面的 ji+1数组末尾
Bob: 我们可以用前序和来省略一些计算,前序和是这样的,假如我们有一个数组[1,2,3,4] 这个数组对应的前序和数组就是[1, 3, 6, 10] 前序和数组中第 i 个元素就是 原数组 中前 i 个元素的和。这样的话,如果我们想要求原数组中 第 j 个元素到第 k 个元素的和,只要用前序和数组中的 第 k 个元素减去 第 j-1 个元素就可以了。
Alice: 这样就能省略计算吗 ?
Bob: 对啊,还是拿[1, 2, 3, 4]来举例子吧, 假如我要算[1, 2] 的和,要计算 1 + 2 ,要计算[1, 2, 3] 的和就要计算1+ 2 + 3,1 + 2 就被重复计算了呀。用前序和算的话,每个元素都只被加了一次,求任意的子数组的和只要一个减法就够了。
Alice: 但是前序和也是O(n^2) 的两重循环啊。
Bob: 是啊,虽然减少了计算量,不过前序和 + 暴力求解应该还是会超时的。
Alice: 这题不是动态规划吗 ? 你还记得动态规划的写法吗 ?
Bob: 不记得了,不过我们可以现写一个啊。
Alice: 我记得动态规划的核心就是递推公式,递推公式有点像数学里面的那个 归纳法 里面的那个从 n 推出 n+1 的情况。
Bob: 从 n 推出 n + 1 ?? 假设我们现在已经求出了前 n 个元素的最大子序和,把第 n+1 个 元素加上 ??
Alice: 不对呀,最大子序和要求的是连续的子数组啊, 前 n 个元素对应的最大子序和 对应的子数组不一定是 以 第 n 个元素结尾的啊。
Bob: 是啊,那就假设我们已经求出了 以 第 n 个元素为结尾的子数组的最大子序和吧,然后现在我们面对一个选择。
Alice: 对于以 第 n+1 个元素为结尾的子数组的子序和 要不要加上 前面 n 个元素的最大子序和 ? 以第 n+1 个元素为结尾的子序和既可以是 只有 第 n+1 个元素, 也可以是连接上 前面的一些元素。
Bob: 对,如果 以第 n 个元素为结尾的子数组的最大子序和 <= 0 就不加了,加了也只能减小以第 n+1 个元素为结尾的子序和。
Alice: w(゚Д゚)w 我们已经把递推公式写出来了。

if dp[n] > 0:
	dp[n+1] = dp[n] + nums[n+1]
else:
   dp[n+1] = nums[n+1]

Bob: w(゚Д゚)w 对啊,(๑•̀ㅂ•́)و✧
Alice: 动态规划就是O(n)的了,好快啊。那你会写什么 更精妙的 分治法吗?
Bob: (・ω・`ll) 明天就会写了。


代码:

Python 方法一 : 动态规划

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if len(nums) == 0:                        
            // 若nums 长度为0,那么最大子序和一定是0
            return 0
        else:
            answers = [x for x in nums]           
            // answers[x] 用来记录 以 nums[x] 为结尾的前面的子数组的最大子序和
            for x in range(1, len(nums)):
                if answers[x-1] <= 0:              
                    // 如果以nums[x-1]为结尾的子数组的最大子序和是个负数,那么以nums[x]为结尾的子数组的最大子序和就是 nums[x] 自身
                    answers[x] = nums[x]
                else:
                    answers[x] = answers[x-1] + nums[x]  
                    // 否则以nums[x] 为结尾的子数组的最大子序和应该加上以nums[x-1]为结尾的子数组的最大子序和,就是延续。
            return max(answers)

Python 方法二: 前序和 + 暴力求解,确认超时 !!!

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        else:
            preSums = [0]
            preSum = 0
            for x in range(0, len(nums)):
                preSum += nums[x]
                preSums.append(preSum)
            ret = nums[0]
            for x in range(0, len(preSums)):
                for z in range(x+1, len(preSums)):
                    if(preSums[z] - preSums[x] > ret):
                        ret = preSums[z] - preSums[x]
            return ret

Java 方法一: 动态规划

class Solution {
    public int maxSubArray(int[] nums) {
        if(nums.length == 0){
            return 0;
        }else{
            int subsequenceSum = nums[0];             // 记录以nums[x]为结尾元素的子数组的最大子序和
            int ret = subsequenceSum;                 // nums数组的最大子序和
            for(int i=1; i<nums.length; ++i){
                if(subsequenceSum <= 0){              // 动态规划之核心的递推公式
                    subsequenceSum = nums[i];
                }else{
                    subsequenceSum += nums[i];
                }
                if(subsequenceSum > ret){             // “打擂台”得到一个最大值
                    ret = subsequenceSum;
                }
            }
            return ret;
        }
    }
}

c++ 方法一: 动态规划

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size() == 0){
            return 0;
        }else{
            int subSum = nums[0];
            int maxSubSum = subSum;
            for(int i=1; i<nums.size(); ++i){
                if(subSum <= 0){
                    subSum = nums[i];
                }else{
                    subSum += nums[i];
                }
                if(maxSubSum < subSum){
                    maxSubSum = subSum;
                }
            }
            return maxSubSum;
        }
    }
};

易错点:

  • 一些测试样例:
[-2,1,-3,4,-1,2,1,-5,4]
[0]
[1,2,3]
[-1,-2, -3]
[-2, 1, 0, 4]
  • 暴力方法可能会超时,不过可以用 数组的 前序和 来试一试。

总结:

最大子序和,将前面积累的和为负的子数组抛弃,将新的元素作为子序和的开头。此之谓,当断而断。


你可能感兴趣的:(#,LeetCode小花园)