题解:学而思编程 删除一个元素
【题目来源】
学而思编程:删除一个元素
【题目描述】
给出一个数组 \(a_1,a_2,⋯,a_n\),删除一个元素后,求它的最大子段和。(子段是指数组中连续的一段元素)
删除的元素可以由你自由选择,但是不能不删除任何元素,输出你能得到的最大的子段和。
【输入】
第 \(1\) 行,\(1\) 个正整数 \(n\)。
第 \(2\) 行,\(n\) 个整数 \(a_1,a_2,⋯,a_n\)。
【输出】
输出能得到的最大的子段和。
【输入样例】
5
9 5 -6 -10 7
【输出样例】
15
【核心思想】
-
问题分析:给定长度为 \(n\) 的数组,需要恰好删除一个元素后,求剩余数组的最大子段和。这是一个线性DP问题,关键在于如何高效计算删除某个位置后的最大子段和。
-
算法选择:
- 正向DP:\(f[i]\) 表示以位置 \(i\) 结尾的最大子段和
- 转移方程:\(f[i] = \max(f[i-1] + a[i], a[i])\)
- 反向DP:\(g[i]\) 表示以位置 \(i\) 开头的最大子段和
- 转移方程:\(g[i] = \max(g[i+1] + a[i], a[i])\)
- 枚举分割点:对于每个删除位置 \(i\),最优解为 \(\max(f[i-1] + g[i+1], f[i-1], g[i+1])\)
- 正向DP:\(f[i]\) 表示以位置 \(i\) 结尾的最大子段和
-
关键步骤:
- 读取输入:\(n\)(数组长度)、\(a[1..n]\)(数组元素)
- 初始化:\(f\) 和 \(g\) 数组初始化为极小值
- 正向DP(\(i\) 从 \(1\) 到 \(n\)):
- \(f[i] = \max(f[i-1] + a[i], a[i])\)
- 反向DP(\(i\) 从 \(n\) 到 \(1\)):
- \(g[i] = \max(g[i+1] + a[i], a[i])\)
- 枚举删除位置(\(i\) 从 \(1\) 到 \(n\)):
- 计算三种情况:左段+右段、仅左段、仅右段
- \(ans = \max(ans, f[i-1] + g[i+1], f[i-1], g[i+1])\)
- 输出结果:最大子段和 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),三次线性遍历
- 空间复杂度:\(O(n)\),存储 \(f\) 和 \(g\) 数组
-
线性DP的核心思想:
- 正向与反向预处理:通过两次DP分别预处理以每个位置结尾/开头的最大子段和
- 分割点枚举:删除位置将数组分为左右两段,预处理后可以 \(O(1)\) 计算每段的最优值
- 状态转移:最大子段和要么接在前一段后面,要么重新开始
- 边界处理:注意 \(i=1\) 和 \(i=n\) 时的边界情况
- 适用于子段和问题、分割点优化、前后缀预处理类问题
【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义长整型别名,便于处理大数据
#define int long long
// ================= 常量与全局变量 =================
const int N = 100005; // 数组最大长度
int n; // 数组长度
int a[N]; // 原始数组
int f[N]; // f[i] 表示以 i 结尾的最大子段和
int g[N]; // g[i] 表示以 i 开头的最大子段和
int ans = -1e18; // 最终答案,初始化为极小值
// ================= 主函数 =================
signed main()
{
// 读取数组长度
cin >> n;
// 读取数组元素
for (int i = 1; i <= n; i++)
cin >> a[i];
// 初始化 f 和 g 数组为极小值
memset(f, -0x3f, sizeof(f));
memset(g, -0x3f, sizeof(g));
// ========= 正向 DP:计算以 i 结尾的最大子段和 =========
f[1] = a[1]; // 第一个元素单独成段
for (int i = 2; i <= n; i++)
f[i] = max(f[i - 1] + a[i], a[i]); // 要么接在前一段后面,要么重新开始
// ========= 反向 DP:计算以 i 开头的最大子段和 =========
g[n] = a[n]; // 最后一个元素单独成段
for (int i = n - 1; i >= 1; i--)
g[i] = max(g[i + 1] + a[i], a[i]); // 要么接在后一段前面,要么重新开始
// ========= 枚举分割点,计算不相交的两段的最大和 =========
for (int i = 1; i <= n; i++)
{
// 取三种情况的最大值:
// 1. 左段以 i-1 结尾 + 右段以 i+1 开头
// 2. 只有左段
// 3. 只有右段
ans = max({ans, f[i - 1] + g[i + 1], f[i - 1], g[i + 1]});
}
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
浙公网安备 33010602011771号