最大字段和问题 C++(穷举、分治法、动态规划)

问题描述

给定由n个整数(包含负整数)组成的序列a1,a2,...,an,求该序列子段和的最大值。规定当所有整数均为负值时定义其最大子段和为0

穷举法

最简单的方法就是穷举法,用一个变量指示求和的开始位置,一个变量指示结束位置,再一个变量指示当前要加和的位置,每一个开始位置对应n-i个结束位置,遍历一遍就能得到最大值

cpp 复制代码
int maxSubArray(int a[], int n) {
	int maxsum = 0;
	for (int i = 0; i < n; i++) {//开始位置
		for (int j = i; j < n; j++) {//结束位置
			int nowsum = 0;
			for (int k = i; k <= j; k++) {
				nowsum += a[k];
				if (nowsum > maxsum)
					maxsum = nowsum;
			}
		}
	}
	return maxsum;
}

算法有三重循环,时间复杂性为O(n^3)

穷举法优化

当字段的开始下标确定后,要计算[i:j]的字段和可以利用上一次计算的[i:j-1]的字段和,加上a[j]就可以了

cpp 复制代码
int maxSubArray2(int a[], int n) {
	int maxsum = 0;
	for (int i = 0; i < n; i++) {//开始位置
		int nowsum = 0;
		for (int j = i; j < n; j++) {//结束位置
			nowsum += a[j];
			if (nowsum > maxsum)
				maxsum = nowsum;
		}
	}
	return maxsum;
}

改进后的时间复杂度为O(n^2)

分治法

该问题也可以用分治法解决

分治策略思想如下:

将所给序列a[1:n]分成长度相同的两端a[1:n/2]a[n/2 +1:n],分别求出这两段的最大子段和,则整体序列a[1:n]的最大子段和有以下三种情况

  • a[1:n]的最大子段和与a[1:n/2]的最大子段和相同
  • a[1:n]的最大子段和与a[n/2 +1:n]的最大子段和相同
  • a[1:n]的最大子段和是a[1:n/2]最后一段加a[n/2 +1:n]最开始一段的和

前两种情况可以递归求得。对于第三种情况,可以发现,a[n/2]和a[n/2 +1]都在最大子段里,我们可以从a[n/2]向左、从a[n/2 +1]向右分别计算两个最大字段和s1和s2,s1+s2就是最大子段和

递归方程

MaxSum ( l o w , h i g h ) = { max ⁡ ( 0 ,   arr [ l o w ] ) if l o w = h i g h max ⁡ { MaxSum ( l o w , m i d ) MaxSum ( m i d + 1 , h i g h ) CrossSum ( l o w , m i d , h i g h ) otherwise \text{MaxSum}(low, high) = \begin{cases} \displaystyle \max\left(0,\, \text{arr}[low]\right) & \text{if } low = high \\ \displaystyle \max \begin{cases} \text{MaxSum}(low, mid) \\ \text{MaxSum}(mid+1, high) \\ \text{CrossSum}(low, mid, high) \end{cases} & \text{otherwise} \end{cases} MaxSum(low,high)=⎩ ⎨ ⎧max(0,arr[low])max⎩ ⎨ ⎧MaxSum(low,mid)MaxSum(mid+1,high)CrossSum(low,mid,high)if low=highotherwise

代码

cpp 复制代码
int maxSubArray3(int a[], int left, int right) {
	if (left == right)
		return a[left];
	int mid = (left + right) / 2;
	int maxleft = maxSubArray3(a, left, mid);
	int maxright = maxSubArray3(a, mid + 1, right);
	int maxleftsum = 0, maxrightsum = 0;
	int nowsum = 0;
	for (int i = mid; i >= left; i--) {
		nowsum += a[i];
		if (nowsum > maxleftsum)
			maxleftsum = nowsum;
	}
	nowsum = 0;
	for (int i = mid + 1; i <= right; i++) {
		nowsum += a[i];
		if (nowsum > maxrightsum)
			maxrightsum = nowsum;
	}
	return max(maxleft, max(maxright, maxleftsum + maxrightsum));
}

T ( n ) = { 2 T ( n 2 ) + O ( n ) n > c O ( 1 ) n ≤ c T(n)=\big \{^{O(1) \quad n\le c}_{2T(\frac{n}{2})+O(n) \quad n>c} T(n)={2T(2n)+O(n)n>cO(1)n≤c

根据master定理,我们得到
T ( n ) = O ( n l o g ) T(n)=O(nlog) T(n)=O(nlog)

比起穷举法的O(n^2)更优了

动态规划

设数组为 a[1..n],定义状态 b[i] 表示以 a[i] 结尾的子段的最大和,则最大字段和为 m a x b j max b_j maxbj

有递归方程:
b [ i ] = { 0 i = 0 ( 边界条件 ) max ⁡ ( b [ i − 1 ] + a [ i ] ,   a [ i ] ) i ≥ 1 b[i] = \begin{cases} 0 & i = 0 \quad (\text{边界条件}) \\ \max(b[i-1] + a[i],\, a[i]) & i \ge 1 \end{cases} b[i]={0max(b[i−1]+a[i],a[i])i=0(边界条件)i≥1

全局最大子段和为所有 b[i] 中的最大值,并与0比较:
MaxSum = max ⁡ ( 0 ,   max ⁡ 1 ≤ i ≤ n b [ i ] ) \text{MaxSum} = \max\left(0,\, \max_{1 \le i \le n} b[i]\right) MaxSum=max(0,1≤i≤nmaxb[i])

计算最优值

使用变量b记录此前最大字段和,如果为负数则当前最大和为a[i],如果为正数则最大和为b+a[i]
代码

cpp 复制代码
int maxSubArray4(int a[], int n) {
	int b = 0;
	int maxsum = 0;
	for (int i = 0; i < n; i++) {
		if (b > 0)
			b += a[i];
		else 
			b = a[i];
		if (b > maxsum)
			maxsum = b;
	}
	return maxsum;
}

构造最优解

使用两个变量startend记录最大字段的起始和结束位置

cpp 复制代码
int maxSubArray4(int a[], int n, int* start, int* end) {
	int b = 0;
	int max_sum = 0;
	int current_start = 0;  // 当前子段起始位置
	*start = *end = -1;     // 默认无效索引

	for (int i = 0; i < n; i++) {
		if (b > 0) {
			b += a[i];
		}
		else {
			b = a[i];
			current_start = i;  // 重置起点
		}
		// 更新全局最大值及索引
		if (b > max_sum) {
			max_sum = b;
			*start = current_start;
			*end = i;
		}
	}
	// 处理全负数情况:返回0且不记录索引
	if (max_sum <= 0) {
		*start = *end = -1;
		return 0;
	}
	return max_sum;
}

时间负责度:O(n)

实例

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//穷举法
int maxSubArray(int a[], int n) {
	int maxsum = 0;
	for (int i = 0; i < n; i++) {//开始位置
		for (int j = i; j < n; j++) {//结束位置
			int nowsum = 0;
			for (int k = i; k <= j; k++) {
				nowsum += a[k];
				if (nowsum > maxsum)
					maxsum = nowsum;
			}
		}
	}
	return maxsum;
}
//穷举法优化
int maxSubArray2(int a[], int n) {
	int maxsum = 0;
	for (int i = 0; i < n; i++) {//开始位置
		int nowsum = 0;
		for (int j = i; j < n; j++) {//结束位置
			nowsum += a[j];
			if (nowsum > maxsum)
				maxsum = nowsum;
		}
	}
	return maxsum;
}
//分治法
int maxSubArray3(int a[], int left, int right) {
	if (left == right)
		return a[left];
	int mid = (left + right) / 2;
	int maxleft = maxSubArray3(a, left, mid);
	int maxright = maxSubArray3(a, mid + 1, right);
	int maxleftsum = 0, maxrightsum = 0;
	int nowsum = 0;
	for (int i = mid; i >= left; i--) {
		nowsum += a[i];
		if (nowsum > maxleftsum)
			maxleftsum = nowsum;

	}
	nowsum = 0;
	for (int i = mid + 1; i <= right; i++) {
		nowsum += a[i];
		if (nowsum > maxrightsum) {
			maxrightsum = nowsum;
		}
	}
	return max(maxleft, max(maxright, maxleftsum + maxrightsum));
}
//动态规划
int maxSubArray4(int a[], int n, int* start, int* end) {
	int b = 0;
	int max_sum = 0;
	int current_start = 0;  // 当前子段起始位置
	*start = *end = -1;     // 默认无效索引

	for (int i = 0; i < n; i++) {
		if (b > 0) {
			b += a[i];
		}
		else {
			b = a[i];
			current_start = i;  // 重置起点
		}
		// 更新全局最大值及索引
		if (b > max_sum) {
			max_sum = b;
			*start = current_start;
			*end = i;
		}
	}
	// 处理全负数情况:返回0且不记录索引
	if (max_sum <= 0) {
		*start = *end = -1;
		return 0;
	}
	return max_sum;
}
int main() {
	int a[] = { 1, -2, 3, 10, -4, 7, 2, -5 };
	cout << maxSubArray(a, 8) << endl;
	cout << maxSubArray2(a, 8) << endl;
	cout << maxSubArray3(a, 0, 7) << endl;
	int start, end;
	cout << maxSubArray4(a, 8, &start, &end) << endl;
	cout <<"start:" << start << " " <<"end:"<< end << endl;
	return 0;
}

运行结果

相关推荐
ahahahahaha23339 分钟前
相似度计算 ccf-csp 2024-2-2
数据结构·c++·算法
故山月白13 分钟前
QT音乐播放器(1):数据库保存歌曲
c++·qt·嵌入式开发
2402_8813193020 分钟前
3.28学习总结
数据结构·学习·算法
夜松云1 小时前
Python数据可视化与数据处理全解析:Matplotlib图形控制与Pandas高效数据分析实战
python·算法·信息可视化·pandas·matplotlib
Wils0nEdwards1 小时前
Leetcode 两数相除
算法·leetcode·职场和发展
程序员老冯头1 小时前
第十一节 MATLAB关系运算符
开发语言·前端·数据结构·算法·matlab
tzc_fly2 小时前
未来基于参考映射的单细胞数据分析
算法·机器学习·数据分析
zero.cyx2 小时前
DFS飞机降落
算法·深度优先
阳光开朗大男孩 = ̄ω ̄=2 小时前
算法基础——二叉树
c++·算法