求连续子数组的最大和

题目描述 给定一个数组 array[1, 4, -5, 9, 8, 3, -6],在这个数字中有多个子数组,子数组和最大的应该是:[9,
8, 3],输出20,再比如数组为[1, -2, 3, 10, -4, 7, 2, -5],和最大的子数组为[3, 10, -4, 7,
2],输出18。

一、暴力解法(时间复杂度O(n^3),空间复杂度O(1))
思路分析
1、可以将给定数组的的所有子数组枚举出来,然后找到子数组和最大的情况,具体来说就是:遍历以A[i]为起点,A[j]为终点的子数组,比较各个子数组的大小,找到最大连续子数组;
最最直观也是最野蛮的办法便是,三个for循环三层遍历,求出数组中每一个子数组的和,最终求出这些子数组的最大的一个值。
记Sum[i, …, j]为数组A中第i个元素到第j个元素的和(其中0 <= i <= j < n),遍历所有可能的Sum[i, …, j],那么时间复杂度为O(N^3):

2、这种方法只是一般思路,时间复杂度太高,为:O(n^3),不应该选择这样的方法。

public int maxSubArray(int[] array) {
    if (array == null || array.length <= 0){
        return 0;
    }
    int max= array[0]; // 此处初始值不能是0
    for(int i = 0;i < array.length; i++) {
        for(int j = i;j < array.length; j++) {
            int sum = 0;
            for(int k = i; k <= j; k++) {
                sum += array[k];
            }
            if(sum > max) {
                max = sum;
            }
        }
    }
    return max;
}

二、在上面的代码基础上加以改进,使时间复杂度变为O(n^2),空间复杂度O(1);先从第一个元素开始向后累加,每次累加后与之前的和比较,保留最大值,再从第二个元素开始向后累加…

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

三、动态规划思想(时间复杂度变为O(n),空间复杂度O(n))
按照常规思路,一般这样定义dp数组:array[0…i]中的最大子数组和为dp[i].如果这样定义的话,整个array数组最大子数组和就是dp[n-1].如何找状态转移方程呢?按照数学归纳法,假设我们知道了 dp[i-1],如何推导出 dp[i] 呢?
实际上是不行的,因为子数组一定是连续的,按照我们当前 dp 数组定义,并不能保证 array[0…i] 中的最大子数组与 array[i+1] 是相邻的,也就没办法从 dp[i] 推导出 dp[i+1]。
所以说我们这样定义 dp 数组是不正确的,无法得到合适的状态转移方程。对于这类子数组问题,我们就要重新定义 dp 数组的含义:
以 array[i] 为结尾的「最大子数组和」为 dp[i]。
这种定义之下,想得到整个 array数组的「最大子数组和」,不能直接返回 dp[n-1],而需要遍历整个 dp 数组:

int res = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
    res = Math.max(res, dp[i]);
}
return res;

使用数学归纳法来找状态转移关系:假设我们已经算出了 dp[i-1],如何推导出 dp[i] 呢?
可以做到,dp[i] 有两种「选择」,要么与前面的相邻子数组连接,形成一个和更大的子数组;要么不与前面的子数组连接,自成一派,自己作为一个子数组。

// 要么自成一派,要么和前面的子数组合并
dp[i] = Math.max(array[i], array[i] + dp[i - 1]);

状态方程 : max( dp[ i ] ) = getMaxSum( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )

动态规划解析:
状态定义: 设动态规划列表 dp ,dp[i] 代表以元素 array[i] 为结尾的连续子数组最大和。

转移方程: 若 dp[i-1] ≤0 ,说明 dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+array[i] 还不如 array[i] 本身大。

当 dp[i - 1] > 0时:执行 dp[i] = dp[i-1] + array[i];
当 dp[i−1]≤0 时:执行 dp[i] = array[i];
初始状态: dp[0] = array[0],即以 array[0]结尾的连续子数组最大和为 array[0] 。

返回值: 返回 dp 列表中的最大值,代表全局最大值。

class Solution {
    public int maxSubArray(int[] array) {
        if (array == null || array.length <= 0){
            return 0;
        }
        int max = array[0]; // 最后要返回的最大子数组的和
        int[] dp = new int[array.length];
        dp[0] = array[0];
        for(int i = 1;i < array.length; i++) {
            dp[i] = getMax(dp[i-1]+ array[i], array[i]); 
            if(dp[i] > max) {
            max = dp[i];
            }
        }
        return max;
    }

    int getMax(int a, int b) {
	    if(a>=b) {
	    	return a;
	    }else{
	    	return b;
	    }
    }
}

四、动态规划思想(时间复杂度变为O(n),空间复杂度O(1)),此方法是将dp数组的空间复杂度降低了,sum其实就是dp[i],只是不将其存到额外的dp数组中。

  int getMax(int a, int b) {
	    if(a>=b) {
	    	return a;
	    }else{
	    	return b;
	    }
  }

  public int maxSubArray(int[] array) {
        if (array == null || array.length <= 0){
            return 0;
        }
        int max = array[0]; // 最后要返回的最大子数组的和
        int sum = array[0]; // 当前最大子数组的和
        for(int i = 1;i < array.length; i++) {
            sum = getMax(sum+ array[i], array[i]);
            if(sum > max) {
            max = sum;
            }
        }
        return max;
    }
        

五、(可输出子串)对于数组array,从array[0]开始逐个进行相加,如果前i-1个元素的最大子数组的和sum大于0的话,就对前i个元素的子数组和最大和sum有正相关性,否则前i-1个元素的最大子数组的和sum小于0的话,就对前i个元素的子数组和最大和sum有负相关性,将前i个元素的最大子数组的和sum舍弃,重新开始从第i个元素开始计算子数组的和sum,将前i个的子数组的最大和sum与最大值max比较,并不停地更替最大值max。

 public int maxSubArray(int[] array) {
    int max = array[0];
    int sum = array[0];
    int start = 0;
    int end = 0; //标志开始和结束位置
    for(int i = 1; i < array.length; i++) {
        if(sum < 0) {
            sum = array[i];
            start = i;
        }else{
            sum += array[i];
        }
        if(sum > max) {
            max = sum;
            end = i;
        }
    }
    return max;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值