最大子数组问题(Maximum Subarray)
最大子数组:和最大的连续非空子数组
暴力
遍历,i从0到n-1,每次计算从i往后的部分的最大子数组,然后求解整个数组的最大子数组
int solve(int *arr, int len){
int maxsum = 0;
for (int i = 0; i<len; i++){
int cursum = 0;
//向后累加
for (int j = i; j<len; j++){
cursum += arr[j];
if (cursum > maxsum)
maxsum = cursum;
}
}
return maxsum;
}
时间复杂度:O(n^2)
分治法
假设求解数组A[low, high]的最大子数组,使用分治法,将数组划分成两个规模尽量相等的子数组。即找到中间位置mid,将数组划分成A[low, mid]和A[mid+1, high]。则最大子数组一定属于以下三种情况:
- 完全位于子数组A[low, mid],low <= i <= j <= mid
- 完全位于子数组A[mid+1, high],mid< i <= j <= high
- 跨越中点,low <= i <= mid < j <= high
最大子数组为以上三种情况中“和”的最大者
可以递归的求解A[low,mid]和A[mid+1, high]的最大子数组,因为这两个问题仍然为最大子数组问题,只是规模减小了。
第三种情况,因为子数组必须跨越中点,所以只需要从中间位置分别向左向右找以mid为边界的最大的子数组即可,即找到A[i,mid]和A[mid+1,j]的最大子数组,然后将其合并即可。
findMaxCrossingSubarray用于求解跨越中间位置的最大子数组,findMaxSubarray用于求解整个数组的最大子数组
int findMaxCrossingSubarray(int a[], int low, int mid, int high) {
int left_max = -INF;
int sum = 0;
for (int i = mid; i >= 0; i--) {
sum += a[i];
if (sum > left_max)
left_max = sum;
}
int right_max = -INF;
sum = 0;
for (int j = mid + 1; j < n; j++) {
sum += a[j];
if (sum > right_max)
right_max = sum;
}
return left_max + right_max;
}
int findMaxSubarray(int a[], int low, int high) {
if (high == low)
return a[low];
int mid = (low + high) >> 1;
int left = findMaxSubarray(a, low, mid);
int right = findMaxSubarray(a, mid + 1, high);
int cross = findMaxCrossingSubarray(a, low, mid, high);
if (left > right && left > cross)
return left;
else if (right > left && right > cross)
return right;
else
return cross;
}
时间复杂度:
T(n)= 2T(n/2)+ O(n),且T(1) = 1。
逐步递推得到时间复杂度T(n) = O(nlogn)
Online
可以将时间复杂度降到O(n)
当sum加上当前元素之后,sum<=0,则直接从下一个位置开始重新累加计算sum
不需要回退到某个位置(即最大子数组一定不包含当前位置之前的元素),因为:
之前子段的前几个元素之和一定为正的(否则之前就直接抛弃了),去掉他们之后的剩余子段和一定小于0,所以还未加上当前位置元素的时候,sum就已经小于0了,所以这样求得的sum一定更小,所以前面的子段全部舍弃
int online(int num[]) {
int sum = 0, ans = 0;
for (int i = 0; i < n; i++) {
if (sum + num[i] > 0) {
sum += num[i];
ans = max(sum, ans);
}
else
sum = 0;
}
return ans;
}