经典算法详解:最大子数组和(暴力 / 分治 / 动态规划 / 线段树)

作为算法入门的经典题目,最大子数组和(最大子段和)问题是学习时间复杂度优化、分治思想、动态规划的绝佳案例。本文将基于 C++ 实现,从暴力枚举到高效的动态规划,再到拓展的线段树解法,一步步拆解思路、解析代码,带你彻底掌握这个经典问题。

一、问题描述

给定一个整数数组(包含正负值),找到一个连续子数组 (子数组不能为空),使其元素之和最大,并返回这个最大和。示例:数组:{-2,11,-4,13,-5,-2}最大子数组:{11,-4,13},最大和为 20


二、解法一:暴力枚举法(基础思路)

暴力法是最直观的思路:枚举所有可能的子数组,计算和并记录最大值。我实现了两种暴力写法,时间复杂度逐步优化。

1. 三重循环暴力(O (n³))

cpp 复制代码
#define ArSize(ar)(sizeof(ar)/sizeof(ar[0]))
// 三重循环暴力破解
int MaxSum1(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	int sum = 0;  // 存储最大和
	int start = -1, end = -1;  // 记录子数组起止下标
	// 枚举起点i
	for (int i = 0; i < n; ++i)
	{
		// 枚举终点j
		for (int j = i; j < n; ++j)
		{
			int thissum = 0;
			// 累加i~j的元素和
			for (int k = i; k <= j; ++k)
			{
				thissum += ar[k];
			}
			// 更新最大值
			if (thissum > sum)
			{
				sum = thissum;
				start = i;
				end = j;
			}
		}
	}
	return sum;
}

代码解析

  • 外层两层循环确定子数组的起止位置[i,j]
  • 最内层循环累加i~j的元素和;
  • 遍历所有子数组后,得到最大和。缺点 :时间复杂度极高O(n³),仅适用于极小数组。

2. 二重循环暴力(O (n²))

优化思路:复用上一次的累加结果,去掉最内层循环:

cpp 复制代码
int MaxSum2(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	int sum = 0;
	int start = -1, end = -1;
	for (int i = 0; i < n; ++i)
	{
		int thissum = 0;
		// 直接累加,无需重新遍历
		for (int j = i; j < n; ++j)
		{
			thissum += ar[j];
			if (thissum > sum)
			{
				sum = thissum;
				start = i;
				end = j;
			}
		}
	}
	return sum;
}

优化点 :固定起点i后,终点j逐步右移,直接累加元素,时间复杂度降至O(n²)


三、解法二:分治策略(O (nlogn))

分治法核心思想:将数组拆分为左右两部分,最大子数组只有三种情况

  1. 完全在左半部分;
  2. 完全在右半部分;
  3. 跨越左右两部分(包含中点)。
cpp 复制代码
// 递归分治函数
int MaxSubSum(const int* ar, int left, int right)
{
	int sum = 0;
	// 递归终止:单个元素
	if (left == right)
	{
		sum = ar[left] > 0 ? ar[left] : 0;
	}
	else
	{
		int mid = (right - left) / 2 + left;  // 计算中点
		// 递归求解左右子问题
		int leftsum = MaxSubSum(ar, left, mid);
		int rightsum = MaxSubSum(ar, mid + 1, right);
		
		// 计算跨越中点的最大和:左半部分(从mid向左)
		int s1 = 0, lefts = 0;
		for (int i = mid; i >= left; --i)
		{
			lefts += ar[i];
			if (lefts > s1)s1 = lefts;
		}
		// 右半部分(从mid+1向右)
		int s2 = 0, rights = 0;
		for (int i = mid + 1; i <= right; ++i)
		{
			rights += ar[i];
			if (rights > s2)s2 = rights;
		}
		// 三种情况取最大值
		sum = s1 + s2;
		sum = max(sum, leftsum);
		sum = max(sum, rightsum);
	}
	return sum;
}

// 分治入口函数
int MaxSum3(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	return MaxSubSum(ar, 0, n - 1);
}

代码解析

  1. 递归拆分数组,直到只剩一个元素;
  2. 分别计算左、右、跨中点三种情况的最大和;
  3. 最终返回三者中的最大值。时间复杂度O(nlogn),效率远高于暴力法。

四、解法三:动态规划(最优解 O (n))

动态规划是解决本题的最优算法 ,核心公式:dp[i] = max(0, dp[i-1]) + nums[i]

  • dp[i]:以第i个元素结尾的最大子数组和;
  • 如果前一个状态dp[i-1]为负,直接舍弃,从当前元素重新开始。

1. 数组版 DP(记录中间过程)

cpp 复制代码
int MaxSum1(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	if (n == 1)return ar[0] > 0 ? ar[0] : 0;
	
	// 动态分配数组存储中间状态
	int* curMaxSum = (int*)calloc(n, sizeof(int));
	int* maxSum = (int*)calloc(n, sizeof(int));
	
	// 初始化:第一个元素
	curMaxSum[0] = ar[0] > 0 ? ar[0] : 0;
	maxSum[0] = curMaxSum[0];
	int max = 0;
	
	for (int i = 1; i < n; ++i)
	{
		// 核心递推公式
		curMaxSum[i] = max(0, curMaxSum[i-1]) + ar[i];
		// 更新全局最大值
		max = max(max, curMaxSum[i]);
		maxSum[i] = max;
	}
	// 释放内存
	free(curMaxSum);
	free(maxSum);
	return max;
}

2. 变量版 DP(空间优化 O (1))

无需数组存储状态,只用两个变量即可完成计算:

cpp 复制代码
int MaxSum2(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	if (n == 1)return ar[0] > 0 ? ar[0] : 0;
	
	// 仅用两个变量,空间复杂度O(1)
	int curMaxSum = ar[0] > 0 ? ar[0] : 0;
	int max = 0;
	
	for (int i = 1; i < n; ++i)
	{
		curMaxSum = max(0, curMaxSum) + ar[i];
		max = max(max, curMaxSum);
	}
	return max;
}

3. 进阶:记录最大子数组起止位置

在 DP 基础上,增加逻辑记录子数组的起止下标:

cpp 复制代码
int MaxSum3(const int* ar, int n)
{
	assert(ar != nullptr);
	if (n < 1)return 0;
	if (n == 1)return ar[0] > 0 ? ar[0] : 0;
	
	int curMaxSum = ar[0] > 0 ? ar[0] : 0;
	int start = -1, end = -1;
	int max = 0;
	
	for (int i = 1; i < n; ++i)
	{
		curMaxSum = max(0, curMaxSum) + ar[i];
		// 重新开始,更新起点
		if (curMaxSum == ar[i]) start = i;
		// 更新最大值和终点
		if (curMaxSum > max)
		{
			max = curMaxSum;
			end = i;
		}
	}
	printf("最大子数组起始下标:%d,终止下标:%d\n", start, end);
	return max;
}

测试示例 :数组:{-2,11,40,-13,-5,8}输出:起始下标 1,终止下标 2,最大和51


五、拓展解法:线段树(区间查询)

线段树适用于多次区间查询的场景,本文实现了基础的区间和查询线段树:

cpp 复制代码
class SegmentTree {
private:
	vector<int>tree;  // 线段树数组
	int n;            // 原数组长度

	// 构建线段树
	void build(const vector<int>& ar, int node, int start, int end)
	{
		if (start == end)
		{
			tree[node] = ar[start];
			return;
		}
		int mid = (start + end) / 2;
		int left = 2*node+1, right = 2*node+2;
		build(ar, left, start, mid);
		build(ar, right, mid+1, end);
		tree[node] = tree[left] + tree[right];
	}

	// 区间查询
	int query(int node, int start, int end, int l, int r)
	{
		if (r<start || l>end) return 0;
		if (l<=start && end<=r) return tree[node];
		int mid = (start+end)/2;
		int left = 2*node+1, right = 2*node+2;
		return query(left,start,mid,l,r) + query(right,mid+1,end,l,r);
	}

public:
	SegmentTree(const vector<int>& ar)
	{
		n = ar.size();
		tree.resize(4*n, 0);
		build(ar, 0, 0, n-1);
	}
	// 对外接口:查询[l,r]区间和
	int rangeQuery(int l, int r)
	{
		return query(0,0,n-1,l,r);
	}
};

图解:

适用场景 :需要频繁查询数组任意区间和的场景,单次查询O(logn)


六、测试主函数

cpp 复制代码
int main()
{
	int ar[] = {-2,11,-4,13,-5,-2};
	int n = ArSize(ar);
	// 测试分治法
	cout << "分治法最大子数组和:" << MaxSum3(ar, n) << endl;
	// 测试动态规划
	cout << "动态规划最大子数组和:" << MaxSum2(ar, n) << endl;
	return 0;
}

运行结果20


七、四种算法对比

表格

算法 时间复杂度 空间复杂度 适用场景
三重暴力 O(n³) O(1) 学习入门、极小数组
二重暴力 O(n²) O(1) 简单场景、小数组
分治策略 O(nlogn) O(logn) 理解分治思想
动态规划 O(n) O(1) 最优解、通用场景
线段树 O (n) 构建 O(4n) 多次区间查询

八、总结

  1. 最大子数组和是动态规划的经典入门题 ,最优解时间复杂度O(n)、空间复杂度O(1)
  2. 暴力法直观但效率低,分治法体现递归拆分思想,动态规划是工业级最优解;
  3. 核心公式:curMaxSum = max(0, curMaxSum) + nums[i],舍弃负收益的前缀。

本文完整实现了所有经典解法,代码可直接运行,适合算法初学者学习~有问题欢迎评论区交流!

相关推荐
呼啦啦5612 小时前
leetcode练习——栈和队列
算法·leetcode·职场和发展
yugi9878382 小时前
基于最大信息熵的粒子群优化算法图像分割(MATLAB实现)
开发语言·算法·matlab
计算机安禾2 小时前
【数据结构与算法】第40篇:图论(四):最短路径——Dijkstra算法与Floyd算法
c语言·数据结构·算法·排序算法·哈希算法·图论·visual studio
SccTsAxR2 小时前
算法进阶:贪心策略证明全攻略与二进制倍增思想深度解析
c++·经验分享·笔记·算法
2301_792674862 小时前
java学习day27(算法)
java·学习·算法
啦啦啦!2 小时前
c++AI大模型接入SDK项目
开发语言·数据结构·c++·人工智能·算法
lcj25112 小时前
【C语言】自定义类型1:结构体
c语言·开发语言·算法
jaysee-sjc2 小时前
十七、Java 高级技术入门全解:JUnit、反射、注解、动态代理
java·开发语言·算法·junit·intellij-idea
yongui478342 小时前
MATLAB模糊控制的粒子群算法(Fuzzy-PSO)实现
数据结构·算法·matlab