LeetCode 面试经典 150 题:删除有序数组中的重复项(双指针思想解法详解)
在数组类算法题中,“删除有序数组中的重复项” 是一道高频基础题,它不仅考察对数组 “原地操作” 的掌握,更能体现对 “双指针” 核心思想的理解。由于数组是有序的,重复元素必然相邻,这一特性为我们提供了高效解题的关键突破口。本文将从题目解读到思路推导,再到代码实现,帮你彻底掌握这道题的最优解法。
一、题目链接与题干解读
首先,你可以通过以下链接直接访问题目,先自行思考解题方向:
LeetCode 题目链接:26.删除有序数组中的重复项
题干核心信息
题目要求如下:
给你一个非严格递增排列的数组 nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果,且这些元素的相对顺序和原数组一致。
不需要考虑数组中超出新长度后面的元素。
示例理解
通过两个典型示例,能更直观地理解题目要求:
- 示例 1:输入 nums = [1,1,2],输出 2,且删除后数组前 2 个元素为 [1,2]。解释:数组是有序的,重复元素 “1” 相邻,删除后保留一个 “1” 和 “2”,新长度为 2。
- 示例 2:输入 nums = [0,0,1,1,1,2,2,3,3,4],输出 5,且删除后数组前 5 个元素为 [0,1,2,3,4]。解释:重复元素均相邻,删除后每个元素只留一个,新长度为 5。
二、解题思路:用变量 k 记录已处理数组长度
由于数组是有序的,重复元素必然相邻,这意味着我们只需判断 “当前元素是否与已处理部分的最后一个元素相同”—— 若不同,则是新的有效元素,需保留;若相同,则是重复元素,需跳过。
这里的变量k扮演了两个关键角色:
- 已处理数组的长度:k的值代表当前已筛选出的 “无重复有效元素” 的个数;
- 有效元素的存放位置:数组前k个位置是已处理的无重复区域,下一个有效元素需放到nums[k]的位置。
整个解题逻辑可概括为:从左到右遍历数组,用k维护无重复区域,每遇到与无重复区域最后一个元素不同的元素,就将其加入无重复区域,同时更新k;遇到重复元素则直接跳过。
1. 步骤拆解与示例演示
以示例 2(nums = [0,0,1,1,1,2,2,3,3,4])为例,一步步拆解整个过程:
步骤 1:初始化变量 k
初始时,已处理的无重复数组为空,因此k = 0(此时nums的前k=0个元素为空,无重复区域尚未有元素)。
步骤 2:遍历数组,筛选无重复元素
遍历数组的每个元素x(从索引 0 开始),核心判断条件是:k == 0(无重复区域为空,当前元素必然是第一个有效元素)或 x != nums[k-1](当前元素与无重复区域的最后一个元素不同,是新的有效元素)。
具体遍历过程如下:
- 索引 0:x = 0,此时k = 0(无重复区域为空)→ 符合条件,将0放到nums[0](本身就在正确位置),k自增 1 → k = 1;
- 索引 1:x = 0,此时k = 1,nums[k-1] = nums[0] = 0 → x == nums[k-1](重复元素),跳过,k保持 1;
- 索引 2:x = 1,nums[k-1] = 0 → x != nums[k-1](新有效元素),将1放到nums[1](此时nums变为[0,1,1,1,1,2,2,3,3,4]),k自增 1 → k = 2;
- 索引 3:x = 1,nums[k-1] = 1 → 重复元素,跳过,k保持 2;
- 索引 4:x = 1,nums[k-1] = 1 → 重复元素,跳过,k保持 2;
- 索引 5:x = 2,nums[k-1] = 1 → 新有效元素,将2放到nums[2](nums变为[0,1,2,1,1,2,2,3,3,4]),k自增 1 → k = 3;
- 索引 6:x = 2,nums[k-1] = 2 → 重复元素,跳过,k保持 3;
- 索引 7:x = 3,nums[k-1] = 2 → 新有效元素,将3放到nums[3](nums变为[0,1,2,3,1,2,2,3,3,4]),k自增 1 → k = 4;
- 索引 8:x = 3,nums[k-1] = 3 → 重复元素,跳过,k保持 4;
- 索引 9:x = 4,nums[k-1] = 3 → 新有效元素,将4放到nums[4](nums变为[0,1,2,3,4,2,2,3,3,4]),k自增 1 → k = 5。
步骤 3:返回 k 的值
遍历结束后,k = 5,这就是删除重复项后数组的新长度。此时nums的前 5 个元素[0,1,2,3,4]就是无重复的有效元素,与示例预期结果完全一致。
2. 关键注意点
- 依赖数组有序性:该解法的前提是数组 “非严格递增”,重复元素必然相邻 —— 若数组无序,此方法不成立(需先排序,但会改变元素相对顺序,不符合题目要求);
- 原地修改:所有操作都在原数组nums上进行,未开辟新数组,符合题目 “原地操作” 和 “O (1) 额外空间” 的要求;
- 元素相对顺序不变:有效元素的存放顺序与原数组中首次出现的顺序一致(如示例 2 中,0、1、2、3、4 的顺序与原数组中首次出现的顺序相同),满足题目 “相对顺序一致” 的要求;
- 无需处理后续元素:题目明确 “不需要考虑数组中超出新长度后面的元素”,因此遍历结束后,无需修改nums[k]及之后的元素,直接返回k即可。
三、复杂度分析
1. 时间复杂度:O (n)
- 我们只对数组nums进行了一次遍历,每个元素只被判断一次(是否为有效元素),最多执行一次赋值操作(将有效元素放到nums[k]);
- 遍历的总次数为数组长度n,无嵌套循环,因此时间复杂度是线性的O(n)。
2. 空间复杂度:O (1)
- 整个过程只用到了一个额外变量k,没有开辟新的数组、列表或其他数据结构;
- 所有操作都在原数组上完成,额外空间的使用与数组长度n无关,因此空间复杂度是常数级的O(1)。
四、代码实现
根据上述思路,我们可以写出简洁高效的代码,以下以 Python和Java 为例:
1,Python
class Solution:def removeDuplicates(self, nums: List[int]) -> int:k = 0for x in nums:if k == 0 or x != nums[k - 1]:nums[k] = xk += 1return k
2,Java
class Solution {public int removeDuplicates(int[] nums) {int k = 0;for (int x : nums) {if (k == 0 || x != nums[k - 1]) {nums[k++] = x;}}return k;}
}
你可以将上述代码复制到 LeetCode 编辑器中,结合题目示例进行测试。
五、总结与拓展
这道题的核心是 “利用数组有序性,用变量 k 维护无重复区域”,本质是快慢双指针的简化版本 —— 若将k看作 “慢指针”(负责维护无重复区域),遍历数组的索引(或元素)看作 “快指针”(负责筛选有效元素),就是典型的快慢双指针解法:快指针遍历数组找有效元素,慢指针存放有效元素,最终慢指针的值就是结果长度。
类似题目拓展
这种 “用双指针维护有效区域” 的思路,在很多有序数组题目中都有应用,例如:
- 80. 删除有序数组中的重复项 II:允许元素最多出现两次,只需修改判断条件为 “当前元素是否与无重复区域的倒数第二个元素相同”,即可扩展解法;
- 27. 移除元素:用慢指针维护 “非目标元素区域”,快指针遍历数组筛选非目标元素,逻辑与本题高度相似。
掌握这种思路,能帮你快速解决一系列 “有序数组原地筛选” 的问题,提升解题的通用性和效率。
希望通过本文的讲解,你能不仅学会 “删除有序数组中的重复项” 这道题的解法,更能理解背后的双指针思想,做到举一反三,轻松应对类似的算法题目。