最大连续子序列和


题目

给定一个由N(1<=n<=100000)个整数元素组成的数组array,数组中可能有正数也可能有负数,整数绝对值不大于1000。数组中一个或多个连续元素可以组成一个子数组,请找出所有连续子数组和的最大值。
样例如下:

输入 输出
1 -2 3 5 -1 2 10
6 -1 5 4 -7 14

暴力枚举法

最普通的方法,效率低下。时间复杂度是:O(n^3)。

具体操作如下:外层for循环枚举子序列的开始,中间for循环枚举子序列的结尾,内层for循环计算首尾之间的序列和,计算所有的序列和,找到最大值。

预处理暴力枚举法

仔细研究暴力枚举法,我们发现效率低下的原因之一是每次都要计算首尾之间的序列和。基于此,可以考虑读入子序列的数据j+1时使用一个数sum来记录该子序列前j项数据之和。
时间复杂度复杂度稍有提升,是:O(n^2)。
具体操作如下:两个for循环枚举子序列的首尾,利用SUM数组计算子序列和,找到最大值。

分治法

也叫递归法。就是把序列分成左右两部分,一般对半分。最大连续子序列的位置存在三种情况:
1. 完全在左半部分
2. 完全在右半部分
3. 跨越左右两部分
以上三者中最大的即是结果。
具体操作时可以将左半部分作为新的输入递归求解,得出左半部分的最大子序列和;右半部分类似。接下来求出包含左半部分中最右边元素的子序列的最大和,和包含右半部分中最左边元素的子序列的最大和,将两者相加即为跨越左右两个部分的最大子序列和。

动态规划法

这种方法简单高效,容易理解。也叫贪心法。时间复杂度:O(n)。算法的关键如下:
1. for循环里的变量i是序列的起点,它有时会跳过若干数。当前计算的序列的和sum一旦为负时,则sum归零。循环变量i加1,从a[i+1]开始加和,即起点跳到了负数和子序列的下一个数字。
2. 一个子序列必然是以正数开头的,因为如果以负数开头,那么去掉这个子序列便得到一个更优解。
3. 一个子序列,如果他的前若干个数字组成的新的个数更少的子序列的和为负数,那么去掉这个子序列便得到一个更优解。
4. 若遇到更大的和则更新max,否则不更新(没有动作)。利用第三个事实,当某个时刻子序列和为负数,归零即相当于去掉子序列所有数字,从下一个数字重新开始。

参考代码

/**
  * 暴力枚举法
  */
public static int t1(int[] array) {
    int max = Integer.MIN_VALUE;
    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;
//                  System.out.println("max = " + max);
            }
        }
    }
    return max;
}

/**
  * 预处理暴力枚举法
  */
public static int t2(int[] array) {
    int max = Integer.MIN_VALUE;
    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;
}

/**
  * 分治法
  */
public static int t3(int[] array) {
    return t3(array, 0, array.length - 1);
}

public static int t3(int[] array, int left, int right) {
    int sum = 0;
    if(left == right) {
        sum = array[left];
    } else {
        int center = (left + right) / 2;
        //左右两部分的和
        int leftSum = t3(array, left, center);
        int rightSum = t3(array, center + 1, right);

        //求包含左半部分最右元素的最大和
        int s1 = Integer.MIN_VALUE;
        int lefts = 0;
        for (int i = center; i >= left; i--) {
            lefts += array[i];
            if (lefts > s1) {
                s1 = lefts;
            }
        }

        //求包含右半部分最左元素的最大和
        int s2 = Integer.MIN_VALUE;
        int rights = 0;
        for (int i = center + 1; i <= right; i++) {
            rights += array[i];
            if (rights > s2) {
                s2 = rights;
            }
        }

        //取三者最大值
        sum = s1 + s2;
        if(sum < leftSum) {
            sum = leftSum;
        }
        if(sum < rightSum) {
            sum = rightSum;
        }
    }
    return sum;
}

/**
  * 动态规划法
  */
public static int t4(int[] array) {
    int max = Integer.MIN_VALUE;
    int sum = 0; //子串和
    for (int i = 0; i < array.length; i++) {
        sum += array[i];
        if(sum > max) {
            max = sum;
        }
        if(sum < 0) {
            sum = 0;
        }
    }
    return max;
}
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值