最大子数组问题描述如下:
一个数组有N个元素,求连续子数组的最大和,并返回其在原数组中的索引位置。例如,[-1,2,1]的最大连续子数组为[2,1],其和为3,索引位置为1和2(数组下标从0开始)。
下面以数组[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7]为例,进行各种算法的说明。
暴力解法
对具有n个元素的数组进行两次遍历,求取任意两个位置之间的数组的和,并记录,取最大值和最大值所在的索引为结果。时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
int find_max_subarray(vector<int> v, int& max_left, int& max_right)
{
int sum = -12345;
int cursum = 0;
for (int i = 0; i < v.size(); i++)
{
cursum = 0;
for (int j = i; j < v.size(); j++)
{
cursum += v[j];
if (cursum > sum)
{
sum = cursum;
max_left = i;
max_right = j;
}
}
}
return sum;
}
int main()
{
int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
vector<int> v(a, a + 16);
int low = 0, high = 15, max_left = 0, max_right = 0;
int sum = find_max_subarray(v, max_left, max_right);
cout << sum <<max_left<<max_right<< endl;
}
分治法
现在将这个大问题分为3个小问题,分的过程如下:首先找到数组的中间位置,mid,然后考虑求解两个子数组A[low,mid]和A[mid+1,high]。那么A[low,high]的任何连续子数组A[i,j]所处的位置必然就是下列三种情况之一:
- 完全位于子数组A[low,mid]中,因此 l o w ≤ i ≤ j ≤ h i g h low \le i \le j \le high low≤i≤j≤high;
- 完全位于子数组A[mid+1,high]中,因此 m i d < i ≤ j ≤ h i g h mid < i \le j \le high mid<i≤j≤high;
- 跨越了中点,因此 l o w ≤ i ≤ m i d < j ≤ h i g h low \le i \le mid < j \le high low≤i≤mid<j≤high
我们可以递归的求解第一种和第二情况,因为这两个子问题依旧是最大子数组问题,只是规模变小了。因此,要求解的实际上就是第三种情况。
那么,给出代码如下:
#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
//求跨越中点的子数组最大值
int find_max_crossing_subarray(vector<int> v, int& low, int& mid, int& high,int& max_left,int& max_right)
{
int left_sum = -12535;
int sum = 0;
for (int i = mid; i >= low; i--)
{
sum += v[i];
if (sum > left_sum)
{
left_sum = sum;
max_left = i;
}
}
int right_sum = -12535;
sum = 0;
for (int i = mid+1; i <=high; i++)
{
sum += v[i];
if (sum > right_sum)
{
right_sum = sum;
max_right = i;
}
}
return left_sum + right_sum;
}
//递归式
int find_maximum_subarray(vector<int> v, int& low, int& high, int& max_left, int& max_right)
{
if (high == low)
return v[low];
else
{
int mid = (high + low) / 2;
int mid_ = mid + 1;
int left_sum = find_maximum_subarray(v, low, mid, max_left, max_right);
int right_sum = find_maximum_subarray(v,mid_, high, max_left, max_right);
int crossing_sum = find_max_crossing_subarray(v, low, mid, high, max_left, max_right);
if ((left_sum >= right_sum) && (left_sum >= crossing_sum))
return left_sum;
else if ((right_sum >= left_sum) && (right_sum >= crossing_sum))
return right_sum;
else
return crossing_sum;
}
}
int main()
{
int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
vector<int> v(a, a + 16);
int low = 0, high = 15, max_left = 0, max_right = 0;
int sum = find_maximum_subarray(v, low, high, max_left, max_right);
cout << sum <<max_left<<max_right<< endl;
}
上述分治法的时间复杂度为 n log ( n ) n\log (n) nlog(n)。
动态规划
若已知前n个数的最大连续子数组,它的和记作dp[n]。那么考虑第n+1个数,此时,A[n+1]只有两种状态,
1.d[n]+A[n+1]<A[n+1],则说明d[n]<0,其最大和就是A[i]本身,此时最大连续子数组只有A[n+1]这一个数;
2.d[n]+A[n+1]>=A[n+1],则说明A[n+1]这个元素也是最大子数组中的一员,则把A[n+1]加入到前面,此时的最大连续子数组从前面某个数A[i]开始,到当前数A[n+1]结束。
#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
//动态规划
int find_max_subarray(vector<int> v, int& max_left, int& max_right)
{
int sum = -12345;
int a = 0;
int tmp_max_left = 0;
for (int i = 0; i < v.size(); i++)
{
if (a + v[i] < v[i])
{
a = v[i];
tmp_max_left = i;
}
else
{
a = a + v[i];
}
if (a > sum)
{
sum = a;
max_right = i;
max_left = tmp_max_left;
}
}
return sum;
}
int main()
{
int a[] = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
vector<int> v(a, a + 16);
int low = 0, high = 15, max_left = 0, max_right = 0;
int sum = find_max_subarray(v, max_left, max_right);
cout << sum <<max_left<<max_right<< endl;
}
上述算法的时间复杂度为 O ( n ) O(n) O(n)。