7-4 最大子段和
给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。
要求算法的时间复杂度为O(n)。
输入格式:
输入有两行:
第一行是n值(1<=n<=10000);
第二行是n个整数。
输出格式:
输出最大子段和。
输入样例:
在这里给出一组输入。例如:
6
-2 11 -4 13 -5 -2
输出样例:
在这里给出相应的输出。例如:
20
一、7-4 最大字段和的分治算法描述
1. 解题思路
本题使用分治法解决,首先将数组分成左右两段,然后递归求解左半段和右半段的最大字段和,再计算跨越中间点的最大字段和,最终返回这三者中的最大值作为整个数组的最大字段和。
2.代码展示
2.1 条件判断
如果只有一个数字,则最大字段和就是该数字的值,且a[left] = a[right],两者可以互换
if(left == right) return a[left];
2.2 取中间值
求出中间值mid,以mid为中心,将原问题分成两个字问题
int mid = (left + right) / 2;
2.3 第一、二种情况:最大子段和可能出现在左半段或右半段
递归调用函数,分别求出左右半段的最大字段和left_max和right_max
int left_max = maxNum(left, mid); //递归调用函数,求出左半段(left到mid)的最大字段和 left_max
int right_max = maxNum(mid + 1, right); //递归调用函数,求出右半段(mid+1到right)的最大字段和 right_max
2.4 第三种情况:最大子段和跨越左右半段
分别求出左右半段的最大子段和再相加,结果即为跨越mid两端的最大子段和fmax
int lmax = a[mid]; //定义lmax的初始值为a[mid],与前面的字段和逐一比较大小
int tmp = a[mid]; //用于记录累加过程中的子段和的值,并与lmax比较大小
for (int i = mid - 1; i >= left; i--) {
tmp += a[i];
if (tmp > lmax) {
lmax = tmp; //更新lmax的值
}
}
int rmax = a[mid + 1]; //定义rmax的初始值为a[mid + 1],与后面的字段和逐一比较大小
tmp = a[mid + 1]; //用于记录累加过程中的子段和的值,并与rmax比较大小
for (int i = mid + 2; i <= right; i++) {
tmp += a[i];
if (tmp > rmax) {
rmax = tmp; //更新rmax的值
}
}
int fmax = lmax + rmax; //左右半段的最大子段和相加
2.5 求得最大子段和
比较三种情况的结果,求得最大子段和max_num并返回该值
int max_num = max(fmax, max(left_max, right_max));
return max_num;
2.6 完整函数代码
int maxNum(int left, int right)
{
if(left == right) return a[left];
int mid = (left + right) / 2;
int left_max = maxNum(left, mid);
int right_max = maxNum(mid + 1, right);
int lmax = a[mid];
int tmp = a[mid];
for (int i = mid - 1; i >= left; i--) {
tmp += a[i];
if (tmp > lmax) {
lmax = tmp;
}
}
int rmax = a[mid + 1];
tmp = a[mid + 1];
for (int i = mid + 2; i <= right; i++) {
tmp += a[i];
if (tmp > rmax) {
rmax = tmp;
}
}
int fmax = lmax + rmax;
int max_num = max(fmax, max(left_max, right_max));
return max_num;
}
3.完整代码
#include<iostream>
using namespace std;
int a[10010];
int maxNum(int left, int right)
{
if(left == right) return a[left];
int mid = (left + right) / 2;
int left_max = maxNum(left, mid);
int right_max = maxNum(mid + 1, right);
int lmax = a[mid];
int tmp = a[mid];
for (int i = mid - 1; i >= left; i--) {
tmp += a[i];
if (tmp > lmax) {
lmax = tmp;
}
}
int rmax = a[mid + 1];
tmp = a[mid + 1];
for (int i = mid + 2; i <= right; i++) {
tmp += a[i];
if (tmp > rmax) {
rmax = tmp;
}
}
int fmax = lmax + rmax;
int max_num = max(fmax, max(left_max, right_max));
return max_num;
}
int main()
{
int n;
int flag = 0;
cin >> n;
for(int j = 0; j < n; j++) {
cin >> a[j];
if(a[j] > 0) {
flag = 1;
}
}
if(flag == 1) {
cout << maxNum(1, n) << endl;
}
else {
cout << "0" << endl;
}
return 0;
}
3.1 运算结果
二、分析该算法的时间复杂度
1. 递归部分:算法使用递归方式来处理左半部分和右半部分。每次递归都将问题分成两半,所以递归树的高度为 O(log n)。
2. 在每个递归步骤中,算法需要线性时间来计算左半部分、右半部分和跨越中间的最大子段和。每个部分的计算都需要遍历一次该部分的元素。因此,每个递归步骤的时间复杂度是 O(n)。
3. 递归树的高度是 O(log n),每个递归步骤的时间复杂度是 O(n),所以总的时间复杂度是 O(n * log n)。
综上所述,该代码的时间复杂度为 O(n * log n)。
三、对分治法的体会和思考
1. 问题分解和递归:分治法的核心思想是将一个大问题分解成多个相似的小问题,并通过递归的方式解决这些小问题。这种方法有助于将问题拆分成更容易处理的部分,同时也使问题的结构更清晰。
2. 空间复杂度:分治法通常需要额外的空间来存储子问题的中间结果。这可能会导致较高的空间复杂度,特别是在递归调用深度较大的情况下。在处理大规模问题时,需要考虑内存消耗。
3. 算法设计的灵活性:分治法的思想可以激发算法设计的灵感,有助于解决各种问题。它不仅仅是一种解决问题的方法,还是一种问题建模和分析的工具。
总之,分治法是一种有力的算法设计和问题求解策略,尤其在大规模问题、并行计算和高效性方面具有潜力。然而,选择使用分治法时需要谨慎考虑问题的特性,以确保它是解决问题的最佳方法之一。然而分治法并不总是最高效的解决方法。在某些情况下,了解其他算法和技术可以帮助优化和改进分治法的应用,如动态规划。因此,在解决问题时,我们需要根据问题的性质和规模选择最合适的算法。