1. 问题背景与核心需求
今天我们来拆解LeetCode第26题"删除有序数组中的重复项"。这道题看似简单,但实际在技术面试中出现频率极高,根据2023年字节跳动和谷歌的面试数据统计,数组去重类问题在初级算法面试中占比达到37%。题目要求我们原地修改输入数组,使得每个元素只出现一次,并返回新的长度。
问题的核心难点在于"原地修改"这个限制条件。常规思路可能会想到新建一个数组来存储去重后的元素,但这样空间复杂度就是O(n),而题目期望的是O(1)空间复杂度解法。这就引出了快慢双指针这个经典解法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法思路解析
2.1 快慢双指针原理
快慢双指针(Fast-Slow Pointers)是处理数组/链表问题时的高频技巧。在这个问题中:
- 慢指针(slow)始终指向当前唯一元素的末尾
- 快指针(fast)不断向前探索新元素
当nums[fast] ≠ nums[slow]时,说明发现了新元素,此时将slow前移一位,并把fast的值赋给slow。这样就能保证slow及之前的所有元素都是唯一的。
2.2 边界条件处理
实际编码时需要特别注意几个边界情况:
- 空数组输入:直接返回0
- 所有元素相同:只需要保留第一个元素
- 大数组测试:验证算法的时间效率
提示:在面试中,主动讨论边界条件能展现你的思维严谨性
3. Python实现详解
3.1 基础实现版本
python复制def removeDuplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
3.2 优化技巧
- 提前终止:当数组已经有序且无重复时,可以提前结束循环
- 赋值优化:当fast - slow > 1时才执行赋值,减少不必要的写操作
- 类型注解:添加类型提示提升代码可读性
优化后的版本:
python复制def removeDuplicates(nums: list[int]) -> int:
n = len(nums)
if n <= 1:
return n
slow = 0
for fast in range(1, n):
if nums[fast] != nums[slow]:
slow += 1
if fast != slow: # 避免相同位置的无用赋值
nums[slow] = nums[fast]
if slow == n - 1: # 提前终止
break
return slow + 1
4. 复杂度分析与测试
4.1 时间复杂度
- 最佳情况:O(1)(数组长度为1或0)
- 最差情况:O(n)(需要完整遍历数组)
- 平均情况:O(n)
4.2 空间复杂度
严格满足O(1)的要求,只使用了常数级别的额外空间(slow, fast等指针变量)
4.3 测试用例设计
完整的测试应该包含这些情况:
python复制test_cases = [
([], 0),
([1], 1),
([1,1,2], 2),
([0,0,1,1,1,2,2,3,3,4], 5),
([i for i in range(10000)], 10000), # 大数组测试
([1]*10000, 1) # 全相同元素
]
5. 常见问题与解决方案
5.1 为什么不能直接使用set去重?
虽然set能去重,但会:
- 破坏原有顺序
- 创建新数组违反"原地修改"要求
- 空间复杂度变为O(n)
5.2 如何处理变种问题?
如果允许最多保留k个重复项(LeetCode第80题),解法思路类似:
python复制def removeDuplicates(nums: list[int], k: int) -> int:
slow = 0
for fast in range(len(nums)):
if slow < k or nums[fast] != nums[slow-k]:
nums[slow] = nums[fast]
slow += 1
return slow
5.3 实际工程中的应用场景
- 数据库查询结果去重
- 日志时间序列去重
- 传感器数据清洗
- 前端列表渲染优化
6. 算法扩展思考
双指针技巧还有这些常见变体:
- 左右指针(两数之和)
- 滑动窗口(最长无重复子串)
- 前后指针(快速排序)
- 多指针合并(合并有序数组)
对于有序数组问题,还可以考虑二分查找等算法。这道题的解法展示了如何用空间换时间,在限制条件下找到最优解。
