算法分析之最大子串和

算法分析之最大子串和

介绍

本文介绍最大子串和问题的解决 主要包括两种基础版 和 取模版 提供经典解法和详细分析。

STL:帮助文档

最大子串和

定义

一、 最大子段和问题 (Maximum Subarray Sum)1. 定义给定一个长度为 n n n 的数组 a 1 ... n a1 \\dots n a1...n,求其所有连续子段和的最大值。数学表达为: max ⁡ 1 ≤ l ≤ r ≤ n ∑ i = l r a i \max_{1 \le l \le r \le n} \sum_{i=l}^{r} a_i 1≤l≤r≤nmaxi=l∑rai

核心基础

二、 核心基础:前缀和 (Prefix Sum)1. 定义前缀和 p r e f i x i prefixi prefixi 表示从数组第 1 1 1 个元素到第 i i i 个元素的累加和: p r e f i x i = ∑ k = 1 i a k prefixi = \sum_{k=1}^{i} a_k prefixi=k=1∑iak2. 核心转化利用前缀和,我们可以将任意区间和的计算简化为两个点的操作: s u m ( l , r ) = p r e f i x r − p r e f i x l − 1 sum(l, r) = prefixr - prefixl-1 sum(l,r)=prefixr−prefixl−1核心思想:这一步实现了 区间问题 → \rightarrow → 两点差值 的转化。这是解决所有子段、区间问题的基石。

Question1


题目OJ

method1
cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int prefix,min_prefix;
        prefix = min_prefix = 0;
        int ans = -1e5;
        for(int i = 0;i<nums.size();i++){
            prefix += nums[i];
            ans = max(ans,prefix-min_prefix);
            min_prefix = min(prefix,min_prefix);
        }
        return ans;
    }
};
analysis

经典prefix前缀和解法:

sum(l,r) = prefixR - prefixL-1

求取ans 最大时 就是要prefixR - prefixL-1(这个最小时 ans 最大)

即我们要找到min_prefix(历史最小值) 求取ans的时候 要实时更新 要取max**

error

前缀和prefix 都是 0 ans 要找最小值的或者nums0 不能是0 因为numsi 里面可能都是负数

method2
cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int sum,ans;
        sum = 0;
        ans = -1e5;
        for(int i = 0;i<nums.size();i++){
           sum = max(nums[i],nums[i]+sum);
           ans = max(sum,ans);
        }
        return ans;
    }
};
analysis

kadane算法:

如果前面的和是负的,就不要它,从当前位置重新开始。即是动态规划

同时需要ans 记录 历史最大值

error

同上

Question2

核心推导

核心逻辑推导我们要最大化区间和取模的结果: max ⁡ { ( s u m ( l , r ) ( m o d p ) ) } \max \{(sum(l, r) \pmod p)\} max{(sum(l,r)(modp))}根据前缀和公式 s u m ( l , r ) = p r e f i x r − p r e f i x l sum(l, r) = prefixr - prefixl sum(l,r)=prefixr−prefixl,在模 p p p 意义下,这个式子等价于: ( p r e f i x r − p r e f i x l + p ) ( m o d p ) (prefixr - prefixl + p) \pmod p (prefixr−prefixl+p)(modp)分情况讨论为了让上述结果尽可能大(即接近 p − 1 p-1 p−1),我们需要根据 p r e f i x r prefixr prefixr 和 p r e f i x l prefixl prefixl 的大小关系分为两种情况:当 p r e f i x r ≥ p r e f i x l prefixr \ge prefixl prefixr≥prefixl 时:结果为 p r e f i x r − p r e f i x l prefixr - prefixl prefixr−prefixl。要使结果最大,我们需要 p r e f i x l prefixl prefixl 越小越好(理想情况是 0 0 0)。当 p r e f i x r < p r e f i x l prefixr < prefixl prefixr<prefixl 时:结果为 p r e f i x r − p r e f i x l + p prefixr - prefixl + p prefixr−prefixl+p。


题目OJ

method

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n;
	long long p;
	cin >> n >> p;

	vector<long long>vc(n);
	for (int i = 0; i < n; i++) {
		cin >> vc[i];
	}

	set<pair<long long, int>>s;

	long long prefix = 0;
	long long ans = 0;
	int L = 0, R = 0;

	s.insert({ 0,-1 });

	for (int i = 0; i < n; i++) {
		prefix = (prefix + vc[i]) % p;

		if (prefix > ans) {
			ans = prefix;
			L = 0;
			R = i;
		}

		auto it = s.upper_bound({prefix,1e9});

		if (it != s.end()) {

			long long val = (prefix - it->first + p) % p;

			if (val > ans) {
				ans = val;
				L = it->second + 1;
				R = i;
			}
			s.insert({ prefix,i });
		}
	}
  cout<<L<<R<<ans<<endl;
}

analysis

1.计算当前前缀和: c u r r = ( c u r r + a i ) ( m o d p ) curr = (curr + ai) \pmod p curr=(curr+ai)(modp)。

2.查找最佳 p r e f i x l prefixl prefixl: 在集合中执行 s.upper_bound(curr)。更新最大值:如果找到了 it = s.upper_bound(curr),则更新 ans = max(ans, (curr - *it + p) % p)。

3.如果没找到(说明 c u r r curr curr 是目前最大的),则更新 ans = max(ans, curr)(即 p r e f i x l = 0 prefixl=0 prefixl=0 的情况)。

4.存入集合: 将 c u r r curr curr 插入 set。
关键结论:

由于情况 2 多加了一个 p p p,它通常比情况 1 更有可能产生更大的结果。要使 p r e f i x r − p r e f i x l + p prefixr - prefixl + p prefixr−prefixl+p 最大,我们需要: p r e f i x l > p r e f i x r prefixl > prefixr prefixl>prefixr (保证进入情况 2) p r e f i x l prefixl prefixl 尽可能小 (减去的项越小,差值越大)因此,对于每一个确定的 p r e f i x r prefixr prefixr,我们需要在已处理的前缀和集合中寻找:最小的、且大于 p r e f i x r prefixr prefixr 的值。

总结

下篇文章 我们将学习算法中的双指针算法。

相关推荐
ambition2024210 小时前
操作系统同步:读者-写者问题与读写公平法详解(附每个 PV 操作含义)
linux·开发语言·数据结构·unix
mCell15 小时前
Lua 编程入门:从基础语法到元表
javascript·算法·lua
wuyk55517 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法
峥无17 小时前
从0到1手撕红黑树:封装实现 my_map 与 my_set(SGI-STL 源码级深度解析)
开发语言·c++·笔记·算法·stl
波特率11520017 小时前
C++新特性---属性说明符与标准属性
开发语言·c++
不会代码的小猴18 小时前
3. 控件学习1
开发语言·c++·笔记·qt·算法
疯狂打码的少年19 小时前
【数据结构】图的存储结构:邻接矩阵与邻接表
数据结构·笔记
203号居民20 小时前
LeetCode hot 100 —41. 缺失的第一个正数
数据结构·算法·leetcode
专注仿真1 天前
问答大模型技术方案算法实现-熵权法融合算法 + 交叉编码器重排算法
人工智能·python·算法·语言模型·问答大模型关键算法·熵权融合算法·交叉编码器重排算法