题解:学而思编程 删除一个元素

【题目来源】

学而思编程:删除一个元素

【题目描述】

给出一个数组 \(a_1,a_2,⋯,a_n\)​,删除一个元素后,求它的最大子段和。(子段是指数组中连续的一段元素)

删除的元素可以由你自由选择,但是不能不删除任何元素,输出你能得到的最大的子段和。

【输入】

\(1\) 行,\(1\) 个正整数 \(n\)

\(2\) 行,\(n\) 个整数 \(a_1,a_2,⋯,a_n\)​。

【输出】

输出能得到的最大的子段和。

【输入样例】

5
9 5 -6 -10 7

【输出样例】

15

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数组,需要恰好删除一个元素后,求剩余数组的最大子段和。这是一个线性DP问题,关键在于如何高效计算删除某个位置后的最大子段和。

  2. 算法选择

    • 正向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])\)
  3. 关键步骤

    • 读取输入\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),三次线性遍历
    • 空间复杂度:\(O(n)\),存储 \(f\)\(g\) 数组
  5. 线性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;
}

【运行结果】

posted @ 2026-06-18 18:03  团爸讲算法  阅读(9)  评论(0)    收藏  举报