题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
算法1
穷举法:穷举所有可能的结果,并取最大值。算法的时间复杂度为O(N^3)。Java实现如下,其中,i表示当前子序列的起点,j表示当前子序列的重点。
public class Solution {
public int FindGreatestSumOfSubArray(int[] a) {
int maxSum = Integer.MIN_VALUE;
for(int i=0;i<a.length;i++) {
for(int j=i;j<a.length;j++) {
int thisSum = 0;
for(int k=i;k<=j;k++) {
thisSum += a[k];
}
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
}
算法2
在算法1的基础上进行改进,去掉最内层循环,时间复杂度降低为O(N^2)。Java实现如下。
public class Solution {
public int FindGreatestSumOfSubArray(int[] a) {
int maxSum = Integer.MIN_VALUE;
for(int i=0;i<a.length;i++) {
int thisSum = 0;
for(int j=i;j<a.length;j++) {
thisSum += a[j];
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
}
算法3
基于递归实现。最大子序列和出现的情况分为3种。
第一种:出现在数组的左半部分
第二种:出现在数组的右半部分
第三种:跨越数组的中间位置从而包含数组左右两个部分的元素
以数组a={4, -3, 5, -2, -1, 2, 6, -2}为例。其左半部分的最大子序列和为6(4,-3,5)。右半部分的最大子序列和为8(2, 8),跨越数组中间部分的子序列和为11(4, -3, 5, -2, -1, 2, 6),所以数组的最大子序列和为11。
此算法每次递归可以将问题求解的规模减小为两个为之前的一半规模的问题的求解,同时加上一个线性时间复杂度的求解过程(求跨越数组中间位置的最大子序列和)。因此T(N)=2T(N/2)+N。
算法的时间复杂度为Nlog(N)。详细证明略,简单特例推导如下:
T(1) = 1;T(2) = 4 = 2 * 2;T(4) = 12 = 4 * 3;T(8) = 32 = 8 * 4 以及T(16) = 80 = 16 *5。可以观察出,当N为2^k时,T(N) = 2^k *(k+1)。即是T(2^k) = 2^k (k+1)。所以T(N) = N * (logN+1) = N * log(N) + N。所以算法的时间复杂度为Nlog(N)。
算法的Java实现如下。
public class Solution {
public int FindGreatestSumOfSubArray(int[] a) {
return FindGreatestSumOfSubArray(a, 0, a.length-1);
}
private int FindGreatestSumOfSubArray(int[] a, int low, int high) {
if(low == high) {
return a[low];
}
int mid = (low + high)/2;
int leftSum = FindGreatestSumOfSubArray(a, low, mid);
int rightSum = FindGreatestSumOfSubArray(a, mid+1, high);
int left = Integer.MIN_VALUE;
int leftTemp = 0;
int right = Integer.MIN_VALUE;
int rightTemp = 0;
for(int i=mid;i>=low;i--) {
leftTemp += a[i];
if(leftTemp > left)
left = leftTemp;
}
for(int i=mid+1;i<=high;i++) {
rightTemp += a[i];
if(rightTemp > right)
right = rightTemp;
}
return max(leftSum, rightSum, left+right);
}
private int max(int a, int b, int c) {
int max = a;
if(b > max)
max = b;
if(c > max)
max = c;
return max;
}
}
算法4
线性对数复杂度并非是本题的最优解。
求一个数组的最大子序列和,那么该最大子序列的第一个元素一定不会是负数,否则,以该序列的第二个元素开头的子序列一定是更有解。与最大子序列不会以负数开头同理,该最大子序列也一定不会以一个和为负数的子序列开头。因此,基于此思想,可以在对数组进行一次遍历后就求得结果。
此算法时间复杂度为N,Java实现如下。
public class Solution {
public int FindGreatestSumOfSubArray(int[] a) {
int maxSum = Integer.MIN_VALUE;
int thisSum = 0;
for(int i=0;i<a.length;i++) {
thisSum += a[i];
if(thisSum > maxSum)
maxSum = thisSum;
else if(thisSum < 0)
thisSum = 0;
}
return maxSum;
}
}