双指针技巧实现数组原地去重:LeetCode经典解法
1. 题目背景与核心需求
这道题目来自力扣(LeetCode)的面试经典150题系列,要求对已排序数组进行原地(in-place)去重操作。在实际编程面试中,数组操作类题目出现频率高达35%,而双指针技巧的应用占比超过60%。题目看似简单,却考察了三个关键能力:对数组内存布局的理解、双指针算法的灵活运用,以及边界条件的处理能力。
注意:面试官通常会追问时间复杂度和空间复杂度的计算过程,以及为什么选择这种解法而不是其他方法。
2. 解法思路与算法选择
2.1 暴力解法分析
最直观的方法是使用额外数组存储唯一元素,空间复杂度O(n)。但题目要求原地修改,这就需要更巧妙的处理方式。
2.2 双指针最优解
采用快慢指针技巧:
- 慢指针slow标记唯一序列的末尾
- 快指针fast遍历整个数组
当nums[fast] ≠ nums[slow]时,将nums[fast]赋值给nums[slow+1],然后slow前进。这种解法时间复杂度O(n),空间复杂度O(1),完美符合题目要求。
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 + 13. 关键实现细节与调试技巧
3.1 边界条件处理
- 空数组情况:直接返回0
- 单元素数组:无需处理,直接返回1
- 全相同元素数组:只需返回1
3.2 指针移动逻辑
慢指针只有在发现新元素时才移动,这保证了它始终指向当前唯一序列的末尾。实测发现,将判断条件改为nums[fast] > nums[slow]可以应对非严格递增的情况。
4. 复杂度分析与优化空间
4.1 时间复杂度证明
快指针完整遍历数组一次,时间复杂度严格为O(n)。通过leetcode的耗时统计可以看到,对于百万级数据量,执行时间仍保持在毫秒级。
4.2 空间复杂度优势
相比使用集合(set)去重需要O(n)空间,该算法仅用常数空间,在内存受限的场景(如嵌入式系统)特别有价值。
5. 实际面试中的变体问题
面试官可能提出的进阶问题包括:
- 如果允许最多保留2个重复项如何修改?
- 对未排序数组该如何处理?
- 如何用函数式编程实现相同功能?
对于问题1,只需增加一个计数器:
def removeDuplicates(nums): if len(nums) <= 2: return len(nums) slow = 2 for fast in range(2, len(nums)): if nums[fast] != nums[slow-2]: nums[slow] = nums[fast] slow += 1 return slow6. 刷题建议与学习路径
建议按照以下顺序掌握数组类题目:
- 双指针基础:删除重复项、移动零
- 滑动窗口:最小覆盖子串
- 多指针处理:三数之和
- 特殊技巧:接雨水问题
对于准备面试的同学,建议每天保持3-5道同类题目的训练量,重点记录每种解法的适用场景和时间/空间复杂度。