最大子序列和是指,给定一组序列,如 [1,-3,2,4,5],求子序列之和的最大值,对于该序列来说,最大子序列之和为 2 + 4 + 5 = 11。
这里的子序列要求是连续的,因此也可以称其为连续子数组最大和。
有几种不同的方法求解最大子序列和问题,但它们的复杂度相差甚远,尤其在面对大量数据的时候。实际上,效率最高的算法非常简短,只需要几行代码,最主要的是理解它的思想。
这是一种比较容易想到的算法,也是一种比较慢的算法(当然还有比它更慢的算法,在此不再赘述)。算法的主要思想是求出所有的连续子序列的和,然后取最大值即可。
注意:对于数组全为负的情况,返回0
示例如下:
public static int base(int[] array) {
int max = 0;
for (int i = 0; i < array.length; i++) {
int temp = 0;
for (int j = i; j < array.length; j++) {
temp += array[j];
if (max < temp)
max = temp;
}
}
return max;
}
可以看到,时间复杂度为 O(n2)。
该问题还可以通过分治法来求解,最大连续子序列和要么出现在数组左半部分,要么出现在数组右半部分,要么横跨左右两半部分。因此求出这三种情况下的最大值就可以得到最大连续子序列和。
这种方法的时间复杂度为 O(nlgn),在此不再赘述。下面介绍一种更好的方法。
该算法由 Jay Kadane 于1984年最先提出,运用了动态规划的思想,因此也称该方法为 Kadane 算法。
算法的时间复杂度为 O(n),能够在线性时间解决问题。虽然代码很简短,但还是需要好好理解这个算法的思想。
注意:对于数组全为负的情况,返回0
代码如下:
public static int get(int[] array) {
int max = 0,temp = 0;
for (int i = 0; i < array.length; i++) {
temp += array[i];
if (temp < 0)
temp = 0;
if (max < temp)
max = temp;
}
return max;
}
以数组 [1,-3,2,4,5] 为例,用图形加以说明:
下面是完整的程序示例:public class MaxSequence {
public static int getMax(int[] array) {
int max = 0,temp = 0;
for (int i = 0; i < array.length; i++) {
temp += array[i];
if (temp < 0)
temp = 0;
if ( max < temp)
max = temp;
}
return max;
}
public static void main(String[] args) {
int[] array1 = {-1,-3,-2,-4,-5};
int[] array2 = {1,-3,2,4,5};
System.out.println(getMax(array1));
System.out.println(getMax(array2));
}
}
输出结果:
0
11
如何求它的最大子序列和呢?
这个问题的解可以分为两种情况:
当然在知道答案之前,我们并不知道解到底属于哪一种情况,因此可以将两种情况下的解都求出来,然后取其中的最大值就可以了。
对于第一种情况,按照之前的算法已经可以顺利求解出来;对于第二种情况,我们不妨换个思路,如果最大子序列跨越了 array[n-1] 到 array[0],那么最小子序列肯定不会出现跨越 array[n-1] 到 array[0] 的情况,如下图所示:
所以,在允许数组跨界(首尾相邻)时,最大子数组的和为下面的最大值
max = { 原问题的最大子数组和,数组所有元素总值 - 最小子数组和 }
求解最小子数组和的方法与最大子数组和的方法正好相反。
示例如下:
public class MaxSequence {
public static int getMax(int[] array) {
int max = 0,temp = 0;
for (int i = 0; i < array.length; i++) {
temp += array[i];
if (temp < 0)
temp = 0;
if ( max < temp)
max = temp;
}
return max;
}
public static int getMin(int[] array) {
int min = 0, temp = 0;
for (int i = 0; i < array.length; i++) {
temp += array[i];
if (temp > 0)
temp = 0;
if (temp < min)
min = temp;
}
return min;
}
public static int getLoopMax(int[] array) {
int max1 = getMax(array);
int min = getMin(array);
int temp = 0;
for (int i = 0; i < array.length; i++) {
temp += array[i];
}
return Math.max(max1, temp - min);
}
public static void main(String[] args) {
int[] array = {1,-3,2,4,5};
System.out.println("原数组:" + Arrays.toString(array));
System.out.println("最大子数组和(非循环):" + getMax(array));
System.out.println("最小子数组和(非循环):" + getMin(array));
System.out.println("最大子数组和(循环):" + getLoopMax(array));
}
}
输出结果:
原数组:[1, -3, 2, 4, 5]
最大子数组和(非循环):11
最小子数组和(非循环):-3
最大子数组和(循环):12
参考链接: