26. 删除有序数组中的重复项(Remove Duplicates from Sorted Array)
频次 ★★★ · 难度 🟢 · 高频:全厂
题目
给定一个有序数组,原地删除重复元素,使每个元素只出现一次,返回新长度。不能使用额外空间,必须 O(1) 额外内存修改输入数组。
示例:
输入: nums = [1, 1, 2]
输出: length = 2, nums 前两位为 [1, 2]
思路
数组已排序,重复元素必然相邻。用快慢双指针:
slow指向下一个不重复元素应放的位置fast遍历数组- 当
nums[fast] != nums[fast-1]时,说明遇到新元素,复制到slow位置
代码
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 1;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[fast - 1]) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}复杂度
- 时间:O(n) — 快指针遍历一次
- 空间:O(1) — 只用了两个指针
边界条件
- 空数组:
nums.length == 0直接返回 0,代码开头已处理。 - 只有一个元素:循环体不会执行(
fast从 1 开始且< 1不成立),直接返回slow = 1,正确。 - 全部重复(如
[1,1,1]):slow始终不递增,最终返回 1。 - 全部不重复(如
[1,2,3]):slow每次都递增,等价于原样返回。
变式
- 允许重复最多两次(LeetCode 80 题类型):判断条件从”是否等于前一个”改为”是否等于
slow-2位置的值”,思路完全一样,只是比较的基准前移了一位。 - 如果数组无序:快慢指针失效,重复元素不一定相邻,需要先排序(多花 O(n log n))或用 HashSet(多花 O(n) 空间)。
易错点
- 题目要求”原地”修改并返回新长度,不需要把
slow之后的元素清零或截断数组——调用方只会看前slow个元素,这一点容易和”移除元素”类题目的输出要求搞混。 - 比较的是
nums[fast] != nums[fast-1](和前一个原始元素比较),而不是和nums[slow-1](已写入的最后一个)比较——虽然对有序数组这两者其实等价,但理解错原理会在写变式题(如允许重复 2 次)时出错。
面试追问
- 为什么这题不需要额外空间,而”无序数组去重”通常要用 HashSet? 因为数组有序保证了重复元素必然相邻,快慢指针能利用这个性质原地压缩;无序数组里重复元素可能相隔很远,只能借助额外的哈希结构或先排序。
- 能不能不改变相对顺序、用双指针从两端向中间夹? 不行,双向夹逼适用于”按值不关心顺序”的场景(如移除特定值),去重需要保持原有相对顺序,只能单向扫描。