题目
给定一个由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;
}