力扣热门100题——最大子数组和

4、最大子数组和

1.问题描述

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

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

2.示例

示例 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

3.提示:

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

4.进阶:

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

5.具体解法(暴力循环,贪心算法,动态规划,分治法)

/*
方法一:暴力循环(不能AC,因为最后一个数组特大,运行时间超出要求)
//自己独立完成的代码,使用了暴力循环的方式,调试了三次终于从只能通过三分之一用例到三分之二到几乎全完成,到最后一次是超长数组通过不了(超出时间限制)
//对于调试代码有了新的理解和经验,一种是自身逻辑入手,一种是跟随失败案例循环一遍自己的代码看哪里哪一步出现了问题
//对于最后的超出时间,已经是超出我的目前水平了,那个数组得有好几千上万个数据,我这种暴力循环的思路,应该是不能解决这种问题的,而且自己写的是暴力循环中的比较笨的
//逻辑不够清晰,只能缝缝补补似的完成需求,不过这是个好的开始
方法一:自己想出来的最简单的暴力遍历,我们以四个数字为例,所有的连续子数组情况无非是(1,12,123,1234,2,23,234,3,34,4),
所以我们去按照这种思路遍历数组即可,定义一个max存储当前的最大值,
用sum去存储每一次计算的新的和,跟max比较,s>max就把s赋值给max,直到遍历结束,返回max
class Solution {
    public int maxSubArray(int[] nums) {
        int max=-999;//因为我后面是比较每个值跟他的大小,他来存储最大值,有一种数组里面全是负数,那么这个max的初始值就不适合设为0;
        int s=0;
        for(int i=0;i<nums.length;i++){//从i等于0开始循环,也就是队列的起始开始位置
            if(i==nums.length-1){//如果是最后一个数,因为我会计算它加他后面的数,如果没有,应该会报错吧,所以我单独把这个拿出来进行判断了
                s=nums[nums.length-1];
                if(s>max){
                    max=s;
                }
            }
            else{
                int k=nums[i];//这个k的作用是存储刚刚加过的数,防止出现,每次都是挨着的两个数相加,而不是全部数相加的情况
                for(int j=i;j<nums.length-1;j++){
                    if(nums[j]>max){
                        max=nums[j];//这里需要单独判断一下是不是第一个数就大于max,要不然如果是[2,-1]的这种情况,那么就会忽略掉max=2
                    }
                    s=k+nums[j+1];
                    k=s;
                    if(s>max){
                        max=s;
                    }

                }
            }
        }
        return max;//当所有的都遍历完成,那么就返回此时的最大值max
    }
}
 */
 /*
 //补上一个可以成功提交的暴力解法
 //可以对比一下,自己写的有多差
class Solution {
    public int maxSubArray(int[] nums) {
        int max = nums[0];
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int sum = 0;
            for (int j = i; j < n; j++) {
                sum += nums[j];
                if (sum > max){
                    max = sum;
                }
            }
        }
        return max;
    }
}
*/

/*
//方法二:动态规划
//首先得去理解这种思路,找每个数作为结尾的数字的情况下的最大值即可
//用f(i)来表示以i为结尾的最大值
//为什么呢,以[1,2,3,4]为例,以1为结尾的就是[1],以2为结尾的可以是[1,2]或者[2],而[1,2]可以看做是问题1加上数字2
//再来一个例子,就更清晰了,以3为结尾的是[1,2,3]或[2,3]或[3],可以看做是问题2的两种情况加上数字3
//我们用一个变量pre来维持f(i) 动态规划状态转移式: f(i) = max{f(i-1) + num,num},我们还需要一个变量来存储接结果返回值

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0, maxAns = nums[0];
        for (int x : nums) {
            pre = Math.max(pre + x, x);//这个是用来算每一个数做结尾的时候的最大值
            maxAns = Math.max(maxAns, pre);//这个是比较i做结尾的最大值和当前的最大值,取大的作为新的最大值
        }
        return maxAns;
    }
}

//动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
//如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
//如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
//每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
//这个理解也很好,就是我前面的sum如果小于零,那再去加新的数字肯定是不好的,所以直接把它舍掉,从最新的数字这里是最大值继续
//也有人说这种叫贪心,上面的才算dp(动态规划)


//下面这个说法非常非常好!!!!!!!!太容易去理解了

//其实这道题可以这么想: 1.假如全是负数,那就是找最大值即可,因为负数肯定越加越大。
//2.如果有正数,则肯定从正数开始计算和,不然前面有负值,和肯定变小了,所以从正数开始。
//3.当和小于零时,这个区间就告一段落了,然后从下一个正数重新开始计算(也就是又回到 2 了)。而 dp 也就体现在这个地方。
//贪心或者说也是一种动态规划的一个代码
class Solution {
    public int maxSubArray(int[] nums) {
        int ans = nums[0];
        int sum = 0;
        for(int num: nums) {
            if(sum > 0) {
                sum += num;
            } else {
                sum = num;
            }
            ans = Math.max(ans, sum);
        }
        return ans;
    }
}

*/

//方法三:分治方法
/*
将原数组划分为左右两个数组后,原数组中拥有最大和的连续子数组的位置有三张情况。
情况1. 原数组中拥有最大和的连续子数组的元素都在左边的子数组中。
情况2. 原数组中拥有最大和的连续子数组的元素都在右边的子数组中。
情况3. 原数组中拥有最大和的连续子数组的元素跨越了左右数组。
分别求出,3中情况的最大和,取最大,就是原数组的连续子数组的最大和。
class Solution {
    public int getMax(int[] nums, int low, int high) {
        // 如果子数组只有一个元素,这个元素就是子树组的最大和。
        if (low == high) {
            return nums[low];
        }
        int mid = low + (high - low) / 2;
        // 求左数组的最大和
        int leftMax = getMax(nums, low, mid);
        // 求右数组的最大和
        int rightMax = getMax(nums, mid + 1, high);
        // 求跨越情况的最大和
        int crossMax = getCrossMax(nums, low, mid, high);
        // 返回最大
        return Math.max(Math.max(leftMax, rightMax), crossMax);
    }
    // 求跨越情况的最大和
    public int getCrossMax(int[] nums, int low, int mid, int high) {
        // 从中间向左走,一直累加,每次累计后都取最大值,最后得到的就是从中间向左累加可得到最大和
        int leftSum = nums[mid];
        int leftMax = nums[mid];
        for (int i = mid - 1; i >= low; i--) {
            leftSum += nums[i];
            leftMax = Math.max(leftMax, leftSum);
        }
        // 从中间向右走,一直累加,每次累计后都取最大值,最后得到的就是从中间向右累加可得到最大和
        int rightSum = nums[mid+1];
        int rightMax = nums[mid+1];
        for (int i = mid + 2; i <= high; i++) {
            rightSum += nums[i];
            rightMax = Math.max(rightMax, rightSum);
        }
        // 向左累加的最大和加上向右累加的最大和,就是跨越情况下的最大和
        return leftMax + rightMax;
    }

    public int maxSubArray(int[] nums) {
        return getMax(nums, 0 , nums.length - 1);
    }
}
 */
/*

//分治思想的另一个代码
//官方给的代码,不如上面的好理解,没有注释
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);
    }
}

 */

6.收获

  • 学习到了动态规划的概念,有了一个接触,但还不够掌握。更详细的解析可以看力扣解答第二个回答的讲解

  • 复习了增强for的概念和使用

  • 开发了自己的思路,第一次接近完全实现一次代码,熟练了对于代码的调试

  • 题目只要求返回结果,不要求得到最大的连续子数组是哪一个。这样的问题通常可以使用「动态规划」解决。

  • 动态规划的思路就是自底向上,从如果只有一个元素的最优解到有n个元素的最优解。

  • 还接触到了贪心算法,对于贪心的理解也有了提升,是一种及时行乐的思想,

  • 将分治算法与递归进行了一个区分理解:一个是不断调用自身,一个是将大问题分成小问题,然后分而治之,在分治中我看到也用到了递归调用的存在,不过递归只是整个分治中的一部分。

  • 还复习了Math类的max方法,自己使用的时候就没有想到,而是自己用if语句去判断来着

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值