当前位置: 首页 > news >正文

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

问题描述

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

穷举法

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

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]就可以了

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

代码

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)nc
根据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[i1]+a[i],a[i])i=0(边界条件)i1
全局最大子段和为所有 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,1inmaxb[i])

计算最优值

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

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记录最大字段的起始和结束位置

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)

实例

#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;
}

运行结果
在这里插入图片描述

相关文章:

  • h5运行在手机浏览器查看控制台信息
  • leetcode41.缺失的第一个正数
  • 数智读书笔记系列025《智能医疗:医学人工智能的未来》
  • Rust安装并配置配置vscode编译器
  • CPP从入门到入土之类和对象Ⅲ
  • UMI-OCR Docker 部署
  • Python:计算机二级:简单应用
  • g对象在flask中主要是用来实现什么
  • 【Linux】Linux_Ubuntu与Windows之间的文件传输
  • 3.26品优购
  • Linux之编辑器vim命令
  • 力扣HOT100之普通数组:53. 最大子数组和
  • Linux编译器gcc/g++使用完全指南:从编译原理到动静态链接
  • 【leetcode hot 100 215】数组中的第K个最大元素
  • kubeadm部署k8s-1.32版本集群(1个master,1个worker)
  • PX4飞控-接收MAVLINK消息(2)-生成MAVLINK_MSG_ID_***.h文件
  • QEMU源码全解析 —— 块设备虚拟化(10)
  • [笔记] 系统分析师 第二章 经济管理与应用数学 (未完待续)
  • Linux系统离线安装ollama【详细版】
  • <command-line>:0:1: error: macro names must be identifiers m
  • 气象干旱黄色预警继续:陕西西南部、河南西南部等地特旱
  • 明查|哈佛大学批改美教育部长来信,红笔标出语法错误?
  • 上海推动AI+文旅深度融合,MaaS平台和产业基地落地徐汇
  • 上海“随申兑”服务平台有哪些功能?已归集800余个惠企政策
  • 西安碑林博物馆票价将调至85元,工作人员:10元属于改扩建期间惠民票
  • “用鲜血和生命凝结的深厚情谊”——习近平主席署名文章中的中俄友好故事