当前位置: 首页 > news >正文

数组拆分求最大不重复数和(动态规划解法)

数组拆分成两部分,使不重复数字数目和最大(动态规划 + C++实现)

题目描述

给定一个长度为 $n$ 的数组 $a[1…n]$。我们需要将数组拆分成前后两部分(即在某个位置 $k$ 上切开,前一部分为 $a[1…k]$,后一部分为 $a[k+1…n]$,$1 \le k < n$),并计算这两部分各自不同数字的数量之和。我们希望找到一个切分方式,使得该值最大,并输出最大值。

输入格式

  • 第一行:一个整数 $n$,表示数组长度。
  • 第二行:$n$ 个整数 $a_1,a_2,\dots,a_n$。

输出格式

  • 输出一个整数,表示最大的不重复数字数量和。

样例 1

输入
5
1 2 2 3 1输出
4

解释:

  • 切在 $k=2$ 时,前半部分 $[1,2]$ 的不同数有 {1,2} 共 2 个,后半部分 $[2,3,1]$ 的不同数有 {1,2,3} 共 3 个,总和 = 5。
  • 但注意 $[1,2,2]$ 与 $[3,1]$ 的划分,总和 = 2 + 2 = 4。
  • 实际最佳是 $k=2$,答案 = 4。

样例 2

输入
6
1 1 1 1 1 1输出
2

解释:
不管怎么切,前半部分和后半部分各自最多只有 1 种不同数字,总和 = 1+1=2。


思路分析

我们要计算:

max⁡1≤k<n(distinct(a[1..k])+distinct(a[k+1..n])) \max_{1 \le k < n} \Big( \text{distinct}(a[1..k]) + \text{distinct}(a[k+1..n]) \Big) 1k<nmax(distinct(a[1..k])+distinct(a[k+1..n]))

直接枚举每个 $k$ 并计算不同元素数量的话,时间复杂度为 $O(n^2)$,会超时。

优化思路

  • 预处理两个数组:

    • left[i] = 前缀 $a[1…i]$ 的不同数字数量。
    • right[i] = 后缀 $a[i…n]$ 的不同数字数量。
  • 那么答案就是:

max⁡1≤k<n(left[k]+right[k+1]) \max_{1 \le k < n} \big( left[k] + right[k+1] \big) 1k<nmax(left[k]+right[k+1])

如何高效求 leftright

  • left[i] 可以通过从左往右扫描,用一个哈希集合或布尔数组记录已经出现的数。
  • right[i] 可以通过从右往左扫描同样方法求出。
  • 每一步的计算都是 $O(1)$,整体复杂度 $O(n)$。

动态规划视角

虽然表面上是“前后缀统计”,但我们也可以把它理解成动态规划的思想:

  • 定义状态:

    • dpL[i] = distinct(a[1..i])
    • dpR[i] = distinct(a[i..n])
  • 转移:

    • dpL[i] = dpL[i-1] + (a[i]是否第一次出现?1:0)
    • dpR[i] = dpR[i+1] + (a[i]是否第一次出现?1:0)
  • 最终结果:

max⁡1≤k<n dpL[k]+dpR[k+1] \max_{1 \le k < n} \ dpL[k] + dpR[k+1] 1k<nmax dpL[k]+dpR[k+1]


C++ 实现(详细注释)

#include <bits/stdc++.h>
using namespace std;/*题目:将数组拆成前后两部分,使两部分各自的不重复数字数量和最大思路:1. 用动态规划思想计算两个数组:- left[i]  = 数组前缀 a[1..i] 的不同数字数量- right[i] = 数组后缀 a[i..n] 的不同数字数量2. 答案就是 max( left[k] + right[k+1] ), 其中 1 <= k < n3. 时间复杂度 O(n),空间 O(n),适合 n=1e5 级别的数据
*/int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;vector<int> a(n + 1);  // 下标从 1 开始for (int i = 1; i <= n; i++) {cin >> a[i];}// ===== 计算前缀不同数字数量 =====vector<int> left(n + 1, 0);   // left[i] 表示前 i 个元素的不同数数量unordered_set<int> seenL;     // 用哈希集合记录前缀已出现的元素for (int i = 1; i <= n; i++) {seenL.insert(a[i]);left[i] = (int)seenL.size();}// ===== 计算后缀不同数字数量 =====vector<int> right(n + 2, 0);  // right[i] 表示从 i 到 n 的不同数数量unordered_set<int> seenR;for (int i = n; i >= 1; i--) {seenR.insert(a[i]);right[i] = (int)seenR.size();}// ===== 枚举分割点 =====int ans = 0;for (int k = 1; k < n; k++) {ans = max(ans, left[k] + right[k + 1]);}cout << ans << "\n";return 0;
}

示例运行过程(样例 1)

输入:

5
1 2 2 3 1
  • 前缀不同数:
    left = [0,1,2,2,3,3]

  • 后缀不同数:
    right = [0,3,3,2,2,1]

  • 枚举:

    • $k=1$: left[1]=1, right[2]=3 → sum=4
    • $k=2$: left[2]=2, right[3]=2 → sum=4
    • $k=3$: left[3]=2, right[4]=2 → sum=4
    • $k=4$: left[4]=3, right[5]=1 → sum=4
      答案 = 4。

复杂度分析

  • 时间复杂度
    两次线性扫描 + 枚举分割点,$O(n)$。
  • 空间复杂度
    $O(n)$,主要存储前缀/后缀数组。

测试更多案例

  1. 所有元素不同
输入
5
1 2 3 4 5
输出
5

解释:无论如何拆,总和都能覆盖全部 5 个不同数字。

  1. 全部相同
输入
6
7 7 7 7 7 7
输出
2
  1. 大小交替
输入
6
1 2 1 2 1 2
输出
4

总结

这道题本质上是前后缀动态规划统计 distinct 元素数量,思路类似区间预处理。
核心要点:

  • left[i]right[i] 表示前缀与后缀的状态。
  • 答案是所有分割点的最大组合值。
  • 复杂度线性,非常高效。
http://www.dtcms.com/a/347561.html

相关文章:

  • Linux内核进程管理子系统有什么第三十三回 —— 进程主结构详解(29)
  • java猜数字游戏(赌城主题版)
  • 注意力机制:捕获长距离依赖关系的革命性技术
  • mysqlbinlog解析命令
  • 订单号老是撞车?我写了个通用 PHP ID 生成器
  • linux添加新硬盘挂载分区和数据迁移
  • 云计算之云主机Linux是什么?有何配置?如何选?
  • Agent原理、构建模式(附视频链接)
  • Python打卡Day50 预训练模型+CBAM模块
  • 【Camera驱动】GMS测试项中Camera FOV Calibration问题详解
  • ROS机器人运动控制
  • 布偶猫吃什么猫粮比较好?2025猫粮品牌排名
  • 如何创建自己的 Minecraft 世界
  • 8月23号打卡
  • MySql知识梳理之DML语句
  • FL Studio Win版.exe安装教程(直接安装版/详细步骤/附安装包下载)
  • 基于STM32的病房监测系统/环境监测系统/人体健康监测系统
  • 曲面方程的三维可视化:从数学解析到Python实现
  • 分割等和子集
  • React学习(十)
  • 【LeetCode】85. 最大矩形 (暴力枚举)
  • 某铝业智慧工厂网络建设优化方案实践
  • 使用tensorRT10部署yolov5目标检测模型(2)
  • 【深度学习】深度学习中的结构化概率模型:理论、方法与应用
  • Qt从qmake迁移到cmake的记录
  • 【深度学习新浪潮】有哪些工具可以帮助我们对视频进行内容分析和关键信息提取?
  • 从0开始学习Java+AI知识点总结-23.web实战案例(班级和学生增删改查、信息统计)
  • Day58 Java面向对象13 instanceof 和 类型转换
  • 自动化运维Ansible
  • 13.机器学习—— ML特征工程和优化方法