题目
输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 求所有子数组的和的最大值,要求时间复杂度为O(n)。
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2, 因此输出为该子数组的和18。
分析
第一个想法肯定就是如果能够把数组的所有子数组之和给列出来,那么就可以很容易的找到最大子数组和,但是这样复杂度太高,那么有没有一种比较简单的方法呢?
首先还是先让我们来分析一下给定的例子数组1, -2, 3, 10, -4, 7, 2, -5。
- 1 + (-2)= -1,那么使用-1 + 3肯定是小于直接从3开始计数的子数组了;
- 3 + 10 + (-4)虽然小于3 + 10,但是-4后面是7, 3 + 10 + (-4) + 7还是在增长的。
那么这里面有没有一个特定的规律,可以用于我们判断在遇到负数的时候是继续往后面选择元素加入该数组呢,还是可以直接从后面的数组开始新的子数组呢?令curSum表示当前子数组的和,maxSum表示最大子数组之和,根据判断我们可以知晓,若curSum + a[i] >= a[i]的话,那么就将a[i]加入该子数组,否则,就从a[i]开始重新计算新的子数组之和。具体代码如下:
#include<iostream>
using namespace std;
int MaxSubArray(int* a, int len){
int curSum = 0;
int maxSum = a[0];
for(int i=0; i<len; i++){
curSum = (a[i] > (a[i]+curSum)) ? a[i] : (a[i]+curSum);
if(curSum > maxSum)
maxSum = curSum;
}
return maxSum;
}
int main(){
int a[] = {1, -2, 3, 10, -4, 7, 2, -5};
int length = sizeof(a)/sizeof(a[0]);
cout << MaxSubArray(a, length) << endl;
return 0;
}
扩展1
如果数组是二维数组,同样要你求最大子数组的和?
若数组是二维数组,那么我们首先要弄清楚二维数组与一维数组本质上有什么区别?
二维数组a[2][4] = {{1, 3, -2, 5},{2, -3, 3, 4}}
一维数组a[8] = {1, 3, -2, 5, 2, -3, 3, 4}
其实二维数组在本质上跟一维数组是可以等同的,例如上面的二维数组中a[1][3] = a[1x4+3] = a[7] = 4, 这样的话那么如果是二维数组求最大子数组的和跟一维数组用的方法是一样的,不过是将数组的形式改变一下。
扩展2
如果是要你求子数组的最大乘积?
分析:(整数数组分析)求最大乘积,本质上是要判断0的数目和负数的数目。
- 若数组中负数的个数为偶数,并且全部非0,那么自然是所有的数相乘最大;
- 若数组中的负数为奇数个,并且全部非0,那么肯定就是要排除一个负数,并且是首部(或者尾部)的那个负数及其前面(或者后面的)的数需要被舍弃掉,这里需要有一个判断;
- 若数组中存在0,那么数组需要以0为界划分为多个数组,然后重复第一步和第二步,计算出最大的乘积。
代码如下:
#include<iostream>
using namespace std;
int LocalMaxSubArray(int *a, int begin, int end){
if(begin == end)
return -1;
int negativeNum = 0;
int negativeArray[end - begin];
int curProduct = 1;
int maxProduct = 0;
for(int i=begin; i<end; i++){
if(a[i] < 0)
negativeArray[negativeNum++] = i;
}
if(negativeNum%2 == 0){
for(int i=begin; i<end; i++)
curProduct *= a[i];
if(curProduct > maxProduct)
maxProduct = curProduct;
} else {
for(int i=begin; i<negativeArray[negativeNum-1]; i++)
curProduct *= a[i];
if(curProduct > maxProduct){
maxProduct = curProduct;
}
curProduct = 1;
for(int i=negativeArray[0]+1; i<end; i++)
curProduct *= a[i];
if(curProduct > maxProduct){
maxProduct = curProduct;
}
}
return maxProduct;
}
int MaxSubArray(int* a, int len){
if(NULL == a || len < 2)
return -1;
int zeroArray[len];
int zeroNum = 0;
int curProduct = 1;
int maxProduct = 0;
int begin = 0;
int end = len;
for(int i=begin; i<end; i++){
if(0 == a[i])
zeroArray[zeroNum++] = i;
}
//首先判断zeroNum是否为0,若为0,则表示数组中没有0这个元素,那么只需要考虑负数是奇数个还是偶数个即可
//若zeroNum不等于0,那么需要将原数组分成zeroNum+1个子数组,然后再判断负数时奇数个还是偶数个
if(0 == zeroNum){
maxProduct = LocalMaxSubArray(a, 0, len);
} else { //当数组中存在0的时候需要考虑子数组的大小
for(int i=0; i<zeroNum; i++){
curProduct = LocalMaxSubArray(a, begin, zeroArray[i]);
if(curProduct > maxProduct)
maxProduct = curProduct;
begin = zeroArray[i]+1;
}
curProduct = LocalMaxSubArray(a, zeroArray[zeroNum-1]+1, len);
if(curProduct > maxProduct)
maxProduct = curProduct;
}
return maxProduct;
}
int main(){
int array[] = {0,-2, 3, 10, 0, 7, 2, -5};
int length = sizeof(array)/sizeof(array[0]);
cout << MaxSubArray(array, length) << endl;
return 0;
}
扩展三
如果同时要求输出子段的开始和结束?
分析:(整数数组分析) 要求子数组的首尾序号,这是一个比较简单的问题,具体代码如下所示:
#include<iostream>
using namespace std;
void MaxSubArray(int *a, int len){
if(NULL == a || len < 1)
return ;
int curSum = 0;
int maxSum = 0;
int begin = 0;
int end = len;
int flagBegin = 0;
int flagEnd = 0;
for(int i=0; i<len; i++){
if(a[i] > a[i]+curSum){
curSum = a[i];
flagBegin = i;
} else{
curSum = a[i]+curSum;
}
if(curSum > maxSum){
maxSum = curSum;
begin = flagBegin;
end = i;
}
}
cout << "begin: " << begin << " end: " << end << " maxSum: " << maxSum << endl;
}
int main(){
//int a[] = {1, -2, 3, 10, -4, 7, 2, -5};
int a[] = {1000, -2, -3, -10, -4, -7, -2, 100};
int length = sizeof(a)/sizeof(a[0]);
MaxSubArray(a, length);
return 0;
}