剑指offer面试题42(java版):连续子数组的最大和

welcome to my blog

剑指offer面试题42(java版):连续子数组的最大和

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路

  • 用sum记录连续n项的和, 当sum小于等于0时, 将sum重置为第n+1项的值, 继续向下遍历
  • 核心: 当sum小于等于0时要重置sum; 等于0是为了处理起始项, 最开始sum=0, 将起始项array[0]赋值给sum
        /*
        动态规划:
        令f(i)表示索引从0到i这段区间中的和的最大值, 对应代码中的currMax
        递推关系
            f(i) = f(i-1) + array[i]  if f(i-1)>0
            f(i) = array[i]  if f(i-1)<=0 or i==0
        */
        
从递推公式开始
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        //dp[i]nums[0,i]上连续子数组的最大和
        //dp[i] = dp[i-1]+nums[i]>nums[i]? dp[i-1]+nums[i] : nums[i]
        //化简后: dp[i] = dp[i-1]>0? dp[i-1]+nums[i] : nums[i]
        int[] dp = new int[n];
        dp[0] = nums[0];
        int max = nums[0];
        for(int i=1; i<n; i++){
            if(dp[i-1] > 0){
                dp[i] = dp[i-1] + nums[i];
            }else{
                dp[i] = nums[i];
            }
            max = Math.max(max, dp[i]);
        }
        return max;
    }
}
//状态压缩
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int max = nums[0];
        int curSum = nums[0];
        for(int i=1; i<n; i++){
            if(curSum>0){
                curSum = curSum + nums[i];
            }else{
                curSum = nums[i];
            }
            max = Math.max(max, curSum);
        }
        return max;
    }
}

第三次做; for循环中的顺序:更新sum, 更新max, 有必要的话第二次更新sum
/*
连续子数组
维护子数组的左右边界
*/
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int max = Integer.MIN_VALUE;
        int sum=0;
        for(int i=0; i<array.length; i++){
            //update
            sum += array[i];
            max = Math.max(max, sum);
            //update
            if(sum <=0)
                sum=0;
        }
        return max;
    }
}
第二遍做,不考虑这种情况:sum+arr[i]会减小,但是sum+arr[i]+arr[i+1]会变大;而是考虑sum+arr[i]和arr[i]的大小关系; 依旧要考虑如何更新左边界
  • f(i) = sum(0…i-1)+arr[i] if sum(0…i-1)+arr[i] > arr[i]
  • f(i) = arr[i] if sum(0…i-1)+arr[i] > arr[i]
  • 动态规划中,“选还是不选”的思想在这里针对的对象不是当前项arr[i],而是sum(0…i-1)
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int currSum=0, maxSum=Integer.MIN_VALUE;
        for(int i=0; i<array.length; i++){
            //这句体现了“选还是不选”sum(0...i-1); 同时也是是否更新左边界
            currSum = Math.max(currSum + array[i], array[i]);
            //是否更新最大值
            maxSum = Math.max(currSum, maxSum);
        }
        return maxSum;
    }
}
第二遍做,根据递归版改成循环版; 难点:什么时候更新左边界?
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int currSum=0, maxSum=Integer.MIN_VALUE;
        for(int i=0; i<array.length; i++){
            currSum = currSum + array[i];//考虑当前项之后的和
            maxSum = currSum > maxSum? currSum : maxSum;//每次都更新最大值
            if(currSum<=0)//是否需要更新左边界
                currSum=0;//更新左边界就是令currSum=0
        }
        return maxSum;
    }
}
第二遍做,感觉难点在于考虑这种情况:sum+arr[i]会减小,但是sum+arr[i]+arr[i+1]会变大,解决方案:更新左边界,令currSum=0; 递归版
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        /*
        什么时候更新子数组的左边界? 
        当当前子数组的元素和<=0时,子数组右边界的下一个元素作为子数组的新左边界
        */
        return Core(array, 0, 0, Integer.MIN_VALUE);
    }
    public int Core(int[] arr, int index, int currSum, int maxSum){
        //base case
        if(index == arr.length)
            return maxSum;
        //
        if(currSum+arr[index]<=0)
            //这里注意不要把currSum赋成arr[index+1]了,
            //currSum只跟0..index有关,跟index+1无关
            //令currSum=0是更新左边界的毕竟操作
            return Core(arr, index+1, 0, currSum+arr[index] > maxSum? currSum+arr[index]:maxSum);
        else
            return Core(arr, index+1, currSum+arr[index], currSum+arr[index] > maxSum? currSum+arr[index]:maxSum);
    }
}
动态规划(循环版1)
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        /*
        连续求了n个数的和, 如果这n个数的和小于等于0, 那么把第n+1个数赋给和. 因为连续的n+1数的和小于等于第n+1个数
        如果这n+1个数的和大于0, 那么把第n+1个数加到和上,直到遍历完数组或者和小于等于零才会重置和
        注意一点: 前面说"如果这n个数的和小于等于0", 为什么不限制为小于0,而是限制为小于等于0, 这是为了处理array[0]用的, sum初始值是0, 所以需要把array[0]赋值给sum
        注意一点: 这个思路没有讨论array[i]值的大小, 只通过观察sum的值进行状态的改变
        注意一点: sum是array[i]之前的连续n项和, 也就是根据sum的大小, 决定array[i]是加到sum上还是直接赋值给sum
        注意一点: 需要设置一个变量记录当前的最大值, 因为sum并不代表当前的最大值, 只是尽力维持连续n项和大于0
        */
        int sum = 0, currMax=Integer.MIN_VALUE;
        for(int i=0; i<array.length; i++){
            if(sum <= 0)
                sum = array[i];
            else //sum>0
                sum += array[i];
            if(sum > currMax)
                currMax = sum;
        }
        return currMax;
    }
}
动态规划(循环版2)
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        /*
        动态规划:
        令f(i)表示索引从0到i这段区间中的和的最大值
        递推关系
            f(i) = f(i-1) + array[i]  if f(i-1)>0
            f(i) = array[i]  if f(i-1)<=0 or i==0
        */
        int sum=0, max=Integer.MIN_VALUE;
        for(int i=0; i<array.length; i++){
            sum = Math.max(sum+array[i], array[i]); // 加sum与不加sum, 体现了动态规划的思想
            max = Math.max(sum, max);
        }
        return max;
    }
}
动态规划(递归版)
  • 需要控制index, sum, max
  • 循环版只用控制sum,max
  • 递归可能会产生大量重复子问题,导致时间效率和空间效率都不高
  • 注意在哪里返回
  • 注意递归中sum和max的处理, max稍微复杂一点
    • currMax=Core(array, index+1, sum+array[index], sum+array[index]>max?sum+array[index]:max);
    • currMax=Core(array, index+1, array[index], array[index]>max?array[index]:max);
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        /*
        动态规划:
        令f(i)表示索引从0到i这段区间中的和的最大值
        递推关系
            f(i) = f(i-1) + array[i]  if f(i-1)>0
            f(i) = array[i]  if f(i-1)<=0 or i==0
        */
        return Core(array, 0, 0, Integer.MIN_VALUE);
    }
    public int Core(int[] array, int index, int sum, int max){
        //recursion finish
        if(index==array.length)
            return max;
        //
        int currMax;
        if(index==0){
            currMax=Core(array, index+1, array[index], array[index]); //注意区分index++和index+1, index++会改变index的值, index+1不会改变index的值
        }
        else{ //index>0
            if(sum>0)
                currMax=Core(array, index+1, sum+array[index], sum+array[index]>max?sum+array[index]:max);
            else // sum<=0
                currMax=Core(array, index+1, array[index], array[index]>max?array[index]:max);
        }
        return currMax;
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值