版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rebornyp/article/details/86014726
最大子数组问题
给定数组,求算其中的最大子数组,要求返回最后的最大子数组的左下标 l 和右下标 r ,以及最大和 s ;
- 思路1:暴力解决,O(n^2)复杂度,这里具体就不用代码实现;
- 思路2:使用分治法,通过假定最大子数组处于左边,中间和右边,来最终得出准确的结果;
- 思路3:使用动态规划算法,最常用的高效算法;
1. 分治法
int main()
{
int a[] = {2,4,-7,5,2,-1,2,-4,3};
int l, r, sum;
l = 0, r = 8;
sum = findMaxSubarray(a, l, r);
printf("左边元素是a[%d]=%d, 右边元素是a[%d]=%d, 最大和sum=%d\n", l, a[l], r, a[r], sum);
return 0;
}
/*
求算最大子数组,传入数组a指针,保存a最左边下标的l地址,最右边下标的r地址;
*/
int findMaxSubarray(int *a, int &l, int&r) {
if(l == r) return a[l];
int mid = l + (r - l) / 2;
int left_min = l, left_max = mid; // 因为l和r都得中间存值,所以每次递归时候,需要用新的变量去存储;
int left_num = findMaxSubarray(a, left_min, left_max); // 最大子数组仅存在左半边时;
int right_min = mid+1, right_max = r; //同上;
int right_num = findMaxSubarray(a, right_min, right_max);
int cross_num = findMaxFromMid(a, l, r); // 计算,当最大子数组贯穿中间mid元素时;
if(left_num >= right_num && left_num >= cross_num) {
l = left_min;
r = left_max;
return left_num;
}
else if(right_num >= left_num && right_num >= cross_num) {
l = right_min;
r = right_max;
return right_num;
}
else return cross_num;
}
/* 对于数组a[],传入最左边下标l,最右边下标r,计算返回子数组中最大和;
* 但是,这里默认是该子数组计算中,必须包含mid元素!
*/
int findMaxFromMid(int *a, int& l, int& r) {
if(l == r) return a[l];
int mid = l + (r - l) / 2;
int sum = a[mid], left_sum = a[mid], left_index = mid; //sum不从0开始取值,使得left_sum数值不必取负无穷;
for(int i=mid-1; i>=l; i--) {
sum += a[i];
if(sum > left_sum) {
left_sum = sum; //找到最大和;
left_index = i; //找到此时的下界;
}
}
sum = a[mid];
int right_sum = a[mid], right_index = mid;
for(int i=mid+1; i<=r; i++) {
sum += a[i];
if(sum > right_sum) {
right_sum = sum;
right_index = i;
}
}
l = left_index; //将求算得到的下界赋给全局变量;下同;
r = right_index;
return left_sum + right_sum - a[mid];; //a[mid]计算了两次,故返回最大和时候要减去一个;
}
-
结果展示:
输入数组:[2,4,-7,5,2,-1,2,-4,3];
输出最大子数组:
-
算法总结:
时间复杂度T(n) = 2T(n/2)+O(n);根据主定理,最终运行时间复杂度为O(nlog(n));
2. 动态规划
int main() {
int a[] = {2,4,-7,5,2,-1,2,-4,3};
int l = 0, r = 8;
int sum = findMaxSubarray(a, l, r);
printf("左边元素是a[%d]=%d, 右边元素是a[%d]=%d, 最大和sum=%d\n", l, a[l], r, a[r], sum);
}
/*
求算最大子数组,传入数组a指针,保存a最左边下标的l地址,最右边下标的r地址;
*/
int findMaxSubarray(int *a, int &l, int &r) {
if(l == r) return a[l];
int cur = a[l], total = a[l]; //cur 为当前最大和值,total为全局最大和值
int left_index = l, right_index = l; //动态记录左右下标
for(int i=l+1; i<=r; i++) { // O(n)级别
// 其实下面这一句代码本来应该为 if(cur + a[i] < a[i]),当前最大和值指的是**必须**包含当前a[i]元素
if(cur < 0) { //那么如果之前的cur拖累的当前a[i],那么久丢掉a[i-1]元素
cur = a[i];
left_index = i; // 记录左下标
}
else cur += a[i]; // 否则加上当前元素
if(cur > total) {
total = cur;
right_index = i; // 记录右下标
}
}
l = left_index;
r = right_index;
return total;
}
- 算法总结:
此算法的时间复杂度为O(n)级别,通过找局部最大,然后求全局最大的思路,最终得出结论;