(d1)有序向量:唯一化

官官方信息技术老师·14 页·深入(追求细节与边界)·0 次浏览·38 天前
有序向量去重双指针复杂度

有序向量:唯一化

看完你能说清 uniqfy 为何只需 O(n),并写出低位紧凑的完整实现

按 空格/→ 演示下一步

问一句
1 / 14 页

全部页面点击任意一页,跳回舞台从这页播放

有序向量去重双指针复杂度

有序向量:唯一化

看完你能说清 uniqfy 为何只需 O(n),并写出低位紧凑的完整实现

1第 1 页 · 有序向量:唯一化

什么是有序向量

上页发现:乱序判重要两两比较,代价高。若向量先排好序呢?就像整理过的书架,相同的书必然紧挨着——找重复只需看邻居,这正是唯一化的前提。

全局有序
元素按非降序排列,任一元素不小于其前驱
重复相邻
相等元素在序列中必然连续出现
局部判重
只需比较相邻元素,无需两两比对
整理后的书架对应 →有序向量

同类书相邻摆放,对应相等元素必然相邻

2第 2 页 · 什么是有序向量

有序性蕴涵的结论

从左到右看有序向量:相等的元素必然相邻,扫描时只需比较相邻元素。

图解渲染中…
c1:核心判定:只比较当前元素与前一元素d1:不重复的元素压入前部有效区间e1:重复元素被忽略,逻辑上等于删除f1:有序性保证重复必相邻,一趟即可完成
3第 3 页 · 有序性蕴涵的结论

低效版算法思路

低效思路:两两比对相邻元素,发现重复就当场删掉,边扫边删。

1
逐对扫描
从 i=1 开始,比较 v[i-1] 与 v[i] 是否相等
2
发现重复
若相等,说明 v[i] 与前一个雷同,需要处理
3
调用 remove
对 v[i] 调用删除,其后元素整体前移一位
4
继续前进
i 加一,扫描下一对,直到向量末尾
4第 4 页 · 低效版算法思路

低效版代码实现

cpp

逐个检查前驱并删除重复元素,看清 remove 的真实开销。

代码高亮加载中…

第 4 行发现重复即调用 remove(i) 删除,但 remove 本身要整体前移后续元素,单次就耗 O(n);最坏情况总复杂度高达 O(n²)。

5第 5 页 · 低效版代码实现

低效版的复杂度分析

沿箭头看:每发现一个重复,删除动作都要把整个后缀前移一次,累计成平方级代价。

图解渲染中…
b1:删除一对重复元素的那次remove调用c1:重复可能连续出现n个,各删一次d1:n次前移×每次O(n)=O(n²)上限
6第 6 页 · 低效版的复杂度分析

换个思路:不删而是留

上一页的低效版总在事后补救:发现重复再删,反复搬动整个后缀。换个思路——既然已经有序,重复必然相邻,为什么不干脆一次扫描,把该留的元素直接搬到前面?

不删,只留
遍历时跳过重复,把保留的元素依次搬到前缀
双指针协作
j 负责探查每个元素,i 标记已保留区间的边界
一次扫描完成
每个元素只访问一次,无需反复移动后缀
展会过关检录对应 →有序向量唯一化

检录员不必清场,只把合规者引到前排,其余原地忽略

7第 7 页 · 换个思路:不删而是留

高效版算法流程

高效版唯一化:j 扫描全表,i 守住已保留区末尾,一趟完成去重。

1
初始化
i 与 j 都指向首元素,已保留区只含这一个元素
2
j 前进扫描
j 逐位后移,比较 A[j] 与 A[i] 是否相等
3
遇到雷同
A[j]==A[i] 时 i 不动,j 继续跳过重复
4
遇到新元素
A[j]>A[i] 时先 i++,再把 A[i]=A[j] 覆盖写入
5
收尾截短
j 扫完后 remove(i+1, n),删除尾部冗余
8第 8 页 · 高效版算法流程

高效版代码实现

cpp

几行代码完成原地唯一化:只留不删,一次遍历搞定。

代码高亮加载中…

j 扫描、i 收留不重复元素,全程一次遍历 O(n);最后改 _size 即原地截断,无需逐个删除。

9第 9 页 · 高效版代码实现

实例演示与分析

从左往右读:j 扫描全表,遇重复只前进 j,遇新元素才推进 i 并赋值,最后截断。

图解渲染中…
c1:i 先加一,再把 V[i]=V[j],新区间右侧补位e1:循环结束后,i 指向保留区间末端,再 i++ 得长度f1:截断:把尾部多余元素丢弃,向量规模缩到 i+1
10第 10 页 · 实例演示与分析

低效版 vs 高效版

两者都在去重,但「删」与「留」一字之差,复杂度差出一个数量级。

低效版
  • 思想:发现重复就逐个删除
  • 移动量:每次删除搬移 O(n) 后缀
  • 复杂度:O(n²),重复多时更慢
高效版
  • 思想:不删,把保留者原地前压
  • 移动量:每个元素最多搬一次
  • 复杂度:O(n),一趟扫描完成
当仅去重一次且规模小时可用低效版;但凡追求性能,应选高效版,因为它消除了重复搬移。
11第 11 页 · 低效版 vs 高效版

自测一题

点击作答

对含重复元素的有序向量做唯一化,为何能在 O(n) 时间内原地完成?

12第 12 页 · 自测一题

本讲要点

  • ✓有序性把重复元素压缩成相邻段,这是前提
  • ✓先找后删:逻辑区间覆盖,避免逐个 remove
  • ✓只需一次遍历、O(n) 时间,无需额外空间
  • ✓区间更新返回新逻辑长度,尾部旧元素不用清
  • ✓无序向量不能这样做,需先排序再唯一化
延伸主题:无序向量唯一化:先排序的代价去重的其他思路:哈希与位图有序向量的其他批量操作
13第 13 页 · 本讲要点

课后思考

先自己琢磨,再对照参考答案——答案只是起点,不是终点。

1如果把「有序」这个前提去掉,唯一化还能这么快吗?瓶颈会卡在哪一步?

参考答案不行。高效版靠相邻元素重合即可去重,本质依赖「相等元素必相邻」。无序时相等元素可散布各处,需哈希或先排序,比较的传递优势消失。

2如果要求把重复元素整体保留为一个区间、其余搬到末尾,该怎样改造高效版?

参考答案仿照「区间划分」套路:双指针扫描,不重复元素前移,重复段聚拢到后缀。返回重复区间的起点,调用者可自由处置这个后缀。

3唯一化后元素的相对次序和原向量一致吗?什么时候这种「稳定」很重要?

参考答案本算法是稳定的:仅前移、不交换,次序保持。当元素带附带数据(如按时间排序的记录)时,稳定性保证了去重不破坏语义。

14第 14 页 · 课后思考