【LeetCode - Java】53. 最大子数组和 (简单 | 并不简单)

1. 题目描述

在这里插入图片描述

2. 解题思路

这道题目看到的第一个瞬间就是想着用循环暴力整它最外层循环是决定子数组的起始位置中层循环是决定子数组的长度最内层循环是用于把该子数组的数全部加起来;最后把这个累加值与历史最大值比较,完成全部循环则得到子数组累加的最大值了。这种解法很好理解,可是,时间超限。我也理解,毕竟这是立方级的时间复杂度了。

于是,第二种解法首要任务是砍掉一层循环!怎么整呢?我想到最内层用于累加的循环是不是可以合并一下?跟谁合并?我可不可以不规定子数组长度,只规定起始位置,让它一直累加到数组的最后一位,每累加一次就比较一次最大值?显然,确实是可以这样子做的,最外层的循环保留了下来,把中层循环与最内层循环合并了。这种解法我自认为是有点巧妙的,可是 ,时间超限Fine!这还是简单题?不给活路了?

由于能力有限,实在想不出来怎么才能再砍掉一层循环,无耻地瞅了一眼官解,那可是醍醐灌顶了!最核心的思想我认为是体现在:

f(n) = max(f(n-1) + num[n], num[n])

其中f(n)是表示数组索引从0n位置中包含n位置的最大子数组的和,理解起来有点烧脑,不过这个想法确实很,顺利地再砍掉了一层循环。

除此以外官解中还有分治法的解答,是利用到了线段树这一种数据结构,我对该内容不太熟悉就不在这里介绍了,有兴趣可以去看看官解。

3. 代码实现

3.1 O(n³)解法(卒 | 时间超限)

public int maxSubArray(int[] nums) {
        int max = nums[0];
        int sum;
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < nums.length - i; j++) {
                sum = 0;
                for (int k = 0; k <= i; k++) {
                    sum += nums[k + j];
                }
                if (sum > max)
                    max = sum;
            }
        }
        return max;
    }

3.2 O(n²)解法(卒 | 时间超限)

public int maxSubArray(int[] nums) {
        int max = nums[0];
        int sum;
        for (int i = 0; i < nums.length; i++) {
            sum = 0;
            for (int j = 0; j < nums.length - i; j++) {
                sum += nums[i+j];
                max = Math.max(sum, max);
            }
        }
        return max;
    }

3.3 动态规划

public int maxSubArray(int[] nums) {
        int max = nums[0];
        int sum = 0;
        for (int num : nums) {
            sum = Math.max(sum + num, num);
            max = Math.max(max, sum);
        }
        return max;
    }

这种解法是本次提交答案中的最优。
在这里插入图片描述

3.4 分治法

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

    private static class Status {
        int lSum, rSum, mSum, iSum;
    }

    private Status get(int[] nums, int i, int length) {
        if (i == length) {
            Status status = new Status();
            status.lSum = nums[i];
            status.rSum = nums[i];
            status.iSum = nums[i];
            status.mSum = nums[i];
            return status;
        }
        Status l = get(nums, i, (i+length) / 2);
        Status r = get(nums, (i+length) / 2 + 1, length);
        return push(l, r);
    }

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

3.5 对比

动态规划法的时间复杂度为O(n),空间复杂度为O(1);分治法的时间复杂度也是O(n),空间复杂度则是O(log n)
在这里插入图片描述

是不是有个疑问,分治法都没有动态规划的性能好,为啥官解中还着重介绍它?其实官解对于这个也有解释,我个人理解是把一个数组作为一个中序遍历的二叉树来储存了,这里贴上来让大家看一下吧。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值