给定一个数组,求这个数组中最大连续子段和:
例如:
Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
(1)首先看到这个题我能想到的暴力破解,就是遍历数组,定义一个最大连续数组和,当前数组和大于则替换
时间复杂度为O(n^2)
//暴力破解O(n^2)
public static int maxSubSum2(int[] a) {
int maxSum=0;
for(int i=0;i<a.length;i++) {
int sum=0;
for(int j=i;j<a.length;j++) {
sum+=a[j];
if(sum>maxSum) {
maxSum=sum;
}
}
}
return maxSum;
}
(2)接下来我们来优化,采用分治法来解决
将一个大问题,划分为两个小问题,2个小问题再划分,直至问题规模小的可以简单解决,那么数组的最大子数组和就分为三种情况:
- 右侧数组的最大子数组和
- 左侧数组的最大子数组和
- 左右两侧数组中间毗邻位置连接形成的子数组的和的最大值(如-6,4,-3,2####5,-6,9,-8,左侧最大值为4,右侧为9,中间毗邻位置从2和5向两侧相加,得到中间毗邻子数组4,-3,2,5,-6,9和为11,三者比较得最大子数组和为11)
递归到数组中只包含一个数字,进行ln(n)次划分,每次划分后进行n次比较,所以算法时间复杂度为n*ln(n),但是还达不到O(n)的要求
public static int maxSubSum3(int[] a) {
return max(a,0,a.length-1);
}
public static int max(int[] arr,int leftIndex,int rightIndex) {
int sum = 0,leftHalfMax=0,rightHalfMax=0;
if(rightIndex-leftIndex==0) {
return arr[leftIndex];
}else {
int center = (leftIndex+rightIndex)/2;
int maxLeft = max(arr,leftIndex,center);
int maxRight = max(arr,center+1,rightIndex);
//以下是查找跨越中间点的最大子序列
//从中间到左侧
for(int i=center;i>=leftIndex;--i) {
sum+=arr[i];
if(sum>leftHalfMax) {
leftHalfMax=sum;
}
}
sum=0;
//从中间到右侧
for(int i=center+1;i<=rightIndex;++i) {
sum+=arr[i];
if(sum>rightHalfMax) {
rightHalfMax=sum;
}
}
return max(maxLeft,maxRight,leftHalfMax+rightHalfMax);
}
}
public static int max(int a,int b,int c) {
return a>b?(a>c?a:c):(b>c?b:c);
}
上述是采用分治法解决的,时间复杂度为n*ln(n)
(3)接下来我们来使用减治法优化这个问题
假设我们当前已经知道一个最大子数组和,现在在该数组后面增加一个数字,新数组的最大子数组和可能是什么呢
- 原数组的最大数组和
- 新增加的数字为正数,和最右侧的子数组形成了一个最大子数组和
然后将这两个数字进行比较即可
public static long maxSubSum4(int[] arr) {
if(arr==null||arr.length<0){
return -1;
}
long maxSum = arr[0];//所有子数组中最大的和
long rightEdge = arr[0];
for(int i=1;i<arr.length;i++) {
if(rightEdge<0) {
rightEdge = arr[i];
}else {
rightEdge+=arr[i];
}
maxSum = getMax(rightEdge,maxSum);
}
return maxSum;
}
public static long getMax(long a,long b) {
return a>b?a:b;
}
这种方法的时间复杂度为O(n)
欢迎大家指正!