题解:学而思编程 删数最大子段和

【题目来源】

学而思编程:删数最大子段和

【题目描述】

给出一个数组 \(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\) 的序列 \(a_1, a_2, \ldots, a_n\),需要删除一个元素后,求剩余数组的最大子段和。等价于求两段不相邻的最大子段和(删除位置 \(i\) 后,左边 \(a[1..i-1]\) 和右边 \(a[i+1..n]\) 各取一段)。这是一个线性动态规划的扩展问题。

  2. 算法选择

    • 前缀DP\(dp1[i]\) 表示以 \(a[i]\) 结尾的最大子段和(正向)
    • 后缀DP\(dp2[i]\) 表示以 \(a[i]\) 开头的最大子段和(反向)
    • 组合枚举:枚举删除位置 \(i\),计算 \(dp1[i-1] + dp2[i+1]\) 的最大值
  3. 关键步骤

    • 特判全负数:若所有 \(a[i] < 0\),答案为 \(\max(a[i])\)(只能选最大的负数)
    • 计算前缀最大子段和:
      • \(dp1[i] = \max(dp1[i-1] + a[i], a[i])\)
      • 表示以 \(a[i]\) 结尾的最大子段和
    • 计算后缀最大子段和:
      • \(dp2[i] = \max(dp2[i+1] + a[i], a[i])\)
      • 表示以 \(a[i]\) 开头的最大子段和
    • 枚举删除位置 \(i\)\(1 \leq i \leq n\)):
      • 左边最大贡献:\(dp1[i-1]\)(以 \(i-1\) 结尾的最大子段)
      • 右边最大贡献:\(dp2[i+1]\)(以 \(i+1\) 开头的最大子段)
      • 更新答案:\(ans = \max(ans, dp1[i-1] + dp2[i+1])\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),三次线性遍历(前缀DP、后缀DP、枚举删除位置)
    • 空间复杂度:\(O(n)\),存储 \(dp1\)\(dp2\) 数组
  5. 前后缀DP组合的核心思想

    • 问题转化:删除一个元素后的最大子段和 = 左边某段 + 右边某段(两段不相邻)
    • 前缀预处理:快速获取以任意位置结尾的最大子段和
    • 后缀预处理:快速获取以任意位置开头的最大子段和
    • 枚举分割点:通过枚举删除位置,组合左右两段的最优解
    • 适用于区间分割、删除/插入元素后的最优解问题

【算法标签】

线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, a[N];  // n: 数组大小,a: 数组元素
int dp1[N], dp2[N];  // dp1: 从前向后的最大子段和,dp2: 从后向前的最大子段和

int main()
{
    cin >> n;  // 读入数组大小
    bool flag = 0;  // 标记是否存在非负数
    int mx = -1e9;  // 记录数组中的最大值
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];  // 读入数组元素
        if (a[i] >= 0)  // 如果存在非负数
        {
            flag = 1;
        }
        mx = max(mx, a[i]);  // 更新最大值
    }

    // 如果所有数都是负数,则最大子段和就是最大的那个负数
    if (flag == 0)
    {
        cout << mx << endl;
        return 0;
    }

    // 计算从前向后的最大子段和
    // dp1[i]表示以a[i]结尾的最大子段和
    dp1[1] = a[1];
    for (int i = 2; i <= n; i++)
    {
        dp1[i] = max(dp1[i - 1] + a[i], a[i]);
    }

    // 计算从后向前的最大子段和
    // dp2[i]表示以a[i]开头的最大子段和
    dp2[1] = a[n];
    for (int i = n; i >= 1; i--)  // 这里有个小问题,应该是从n-1开始
    {
        dp2[i] = max(dp2[i + 1] + a[i], a[i]);
    }

    int maxn = -1e9;  // 记录最大两段子段和
    for (int i = 1; i <= n; i++)
    {
        // 计算不相邻的两段最大子段和
        // 注意:这里dp1[i-1]和dp2[i+1]分别表示第i个元素左边和右边的最大子段和
        maxn = max(maxn, dp1[i - 1] + dp2[i + 1]);
    }

    cout << maxn << endl;  // 输出结果
    return 0;
}

【运行结果】

5
9 5 -6 -10 7
15
posted @ 2026-06-14 10:45  团爸讲算法  阅读(6)  评论(0)    收藏  举报