简单算法题我重拳出击 | leetcode.[53]最大子序和

362 阅读3分钟

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

原题

最大子序和

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

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

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

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

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


重拳出击

并不能快速出击,这简单题怎么这么难啊,淦。

首先仔细看了看题目,关键字是连续子数组

暴力

正经人能想到是暴力解法。 一看题目,是个数组,那么就能通过循环来获得它的连续子数组(不需要拿出来所有的子数组,那样的话实在太暴力了),写出来应该是一个双层for循环。max的初始值要足够的小,不要设置0,防止意外发生。

int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++)
{
    int sum = 0;
    for (int j = i; j < nums.length; j++)
    {
        sum = sum + nums[j];
        //因为取的是连续子数组的和,所以这里一个一个往后头加,然后找出来和最大的就行了
        if (sum > max) max = sum;
    }
}
//循环结束后max的值就是本题的答案

^正常情况到这就结束了,但是字数不太够,没得法子^

动态规划

一看题目下面有个标签[动态规划],发现这题居然还能用动态规划,它不写我就不知道。菜主要就是菜在这里。

然后开始套动态规划题目的板子去写:

  • 边界条件

    题目写了nums.length>=1 那这个就没什么特殊的边界了,就记一下第一个值用来推算就行。

    dp(n)={nums[0] n=1??? n>1dp(n)=\left\{ \begin{aligned} nums[0] & &\ n = 1 \\ ??? & & \ n > 1 \\ \end{aligned} \right.
  • 子问题

    一个长度为 n 的数组,它最大和的连续子数组的结尾可能在中间,也可能在两端。这波把子问题设置为:以nums[i]结尾的最大子数组

    然后人脑推算一下,这是一个长度为 m (0 < m << n) 的数组,假如以它第 m-1 个元素为结尾的子数组是最大的。那么,往它后面走一格,长度为 m + 1 ,这时候最大连续子数组的和。就会与第 m 个元素有关,这个元素可能大于之前那个连续子数组或者小于它 。这样,就会有两个结果:

    1. 子数组和比 m 大,继续比较第 m + 1个元素
    2. 子数组和比 m 小,放弃子数组,从第m个元素重新累计

    上头这个结论是能够一直往后面计算的,直到 m = n 。最终留下的那个子数组的和就是最大的。

  • 方程

    根据上一条,可以得到如下:

    dp(nums[i])={nums[0] n=1MAX(nums[i],dp[i1]+nums[i]) n>1dp(nums[i])=\left\{ \begin{aligned} nums[0] & &\ n = 1 \\ MAX(nums[i], dp[i-1]+nums[i]) & & \ n > 1 \\ \end{aligned} \right.

    当数组 nums[i] 遍历结束之后,会得到一个 dp[i] 数组中存的是以nums[i]结尾的最大子数组的和,为了得到答案,需要在遍历一次。

  • 结合各种条件写代码

        public int maxSubArray(int[] nums) {
            //动态规划嘛
            if (nums.length == 1) return nums[0];
            //状态:以num[i]结尾的最大子数组 为 dp[i]
            int[] dp=new int[nums.length];
            dp[0] = nums[0];
            //状态转移方程:MAX(nums[i], dp[i-1]+nums[i])
            for (int i=1;i<nums.length;i++){
                dp[i] = Math.max(nums[i],dp[i-1]+nums[i]);
            }
            //遍历dp数组找到最大的那个
            //初始值设置成最小的,有可能数组里头全是负数
            int res = Integer.MIN_VALUE;
            for (int j : dp) {
                res = Math.max(res, j);
            }
            return res;
        }
    

  • 其实那个子问题还能以第 m 个元素是否大于零去做判断,又是一种不太一样的方法
  • 其实我这个答案还能进行空间优化
  • 其实原题目要我们用分治法,但是我不会