LeetCode 面试经典 150 题:移除元素(双指针思想优化解法详解)
在数组类算法题目中,“移除元素” 是一道考察 “原地操作” 能力的基础题,其核心是在不借助额外数组(或仅用常数级额外空间)的前提下,完成元素的筛选与保留。本文将围绕这道题的最优解法展开,从题目理解到思路推导,再到代码实现,帮你掌握 “用变量记录有效元素个数” 这一高效解题技巧。
一、题目链接与题干解读
首先,建议你先通过以下链接熟悉题目,尝试独立思考解题方向:
LeetCode 题目链接:27. 移除元素
题干核心信息
题目要求如下:
给你一个数组 nums 和一个值 val,请你原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例理解
通过两个典型示例,能更直观理解题目要求:
- 示例 1:输入 nums = [3,2,2,3],val = 3,输出 2,且移除后数组前 2 个元素为 [2,2]。解释:需要删除所有值为 3 的元素,剩余 2 个有效元素,返回长度 2 即可,无需关注数组后续元素。
- 示例 2:输入 nums = [0,1,2,2,3,0,4,2],val = 2,输出 5,且移除后数组前 5 个元素为 [0,1,3,0,4]。解释:删除所有值为 2 的元素后,剩余 5 个有效元素,返回长度 5。
二、解题思路:用变量 k 记录有效元素个数
拿到这道题,很多人可能会想到 “遍历数组,遇到等于 val 的元素就删除”,但数组的删除操作(如 Python 中的pop)会导致后续元素前移,时间复杂度可能达到O(n²)。而 “用变量 k 记录有效元素个数” 的方法,本质是双指针思想的简化,能在O(n)时间内完成操作,且空间复杂度为O(1)。
1. 核心逻辑:筛选有效元素并 “前移”
我们可以把变量k看作两个角色:
- 一是 “有效元素计数器”:记录当前已找到的、不等于val的元素个数;
- 二是 “有效元素存放指针”:指向数组中 “下一个有效元素应该存放的位置”(即索引k)。
整个解题逻辑可概括为:遍历数组,每遇到一个不等于val的元素,就把它放到索引k的位置,然后k加 1;遇到等于val的元素则直接跳过。最终k的值就是移除元素后数组的新长度,数组前k个元素就是筛选后的有效元素。
2. 步骤拆解与示例演示
以示例 2(nums = [0,1,2,2,3,0,4,2],val = 2)为例,一步步看过程:
步骤 1:初始化变量 k
一开始,还未遍历数组,有效元素个数为 0,所以k = 0(此时k指向索引 0,即第一个有效元素的存放位置)。
步骤 2:遍历数组,筛选有效元素
遍历数组的每个元素x(从索引 0 开始),判断x是否等于val:
- 索引 0:x = 0,不等于 2 → 把0放到nums[0](本身就在正确位置),k加 1 → k = 1;
- 索引 1:x = 1,不等于 2 → 把1放到nums[1],k加 1 → k = 2;
- 索引 2:x = 2,等于 2 → 跳过,k保持 2;
- 索引 3:x = 2,等于 2 → 跳过,k保持 2;
- 索引 4:x = 3,不等于 2 → 把3放到nums[2](此时nums变为[0,1,3,2,3,0,4,2]),k加 1 → k = 3;
- 索引 5:x = 0,不等于 2 → 把0放到nums[3](nums变为[0,1,3,0,3,0,4,2]),k加 1 → k = 4;
- 索引 6:x = 4,不等于 2 → 把4放到nums[4](nums变为[0,1,3,0,4,0,4,2]),k加 1 → k = 5;
- 索引 7:x = 2,等于 2 → 跳过,k保持 5。
步骤 3:返回 k 的值
遍历结束后,k = 5,这就是移除元素后数组的新长度。此时数组前 5 个元素[0,1,3,0,4]就是有效元素,与示例预期结果一致。
3. 关键注意点
- 原地修改:整个过程中,我们没有创建新数组,所有操作都在原数组nums上进行,符合题目 “原地修改” 的要求;
- 元素顺序:这种方法会保持有效元素的相对顺序(如示例 2 中,0、1、3、0、4 的顺序与原数组中有效元素的出现顺序一致),但题目允许元素顺序改变,因此即使顺序变化也不影响正确性;
- 无需处理后续元素:题目明确 “不需要考虑数组中超出新长度后面的元素”,因此遍历结束后,无需管nums[k]及之后的元素,直接返回k即可。
三、复杂度分析
1. 时间复杂度:O (n)
- 我们只对数组nums进行了一次遍历,每个元素只被判断一次(是否等于val),最多执行一次赋值操作(将有效元素放到nums[k]);
- 遍历的总次数为数组长度n,因此时间复杂度是线性的O(n)。
2. 空间复杂度:O (1)
- 整个过程只用到了一个额外变量k,没有开辟新的数组、列表或其他数据结构;
- 所有操作都在原数组上完成,额外空间的使用与数组长度n无关,因此空间复杂度是常数级的O(1)。
四、代码实现(预留位置)
根据上述思路,我们可以写出简洁高效的代码,以下以 Python 为例(其他语言如 Java、C++ 逻辑一致,只需调整语法):
1,Python3
class Solution:def removeElement(self, nums: List[int], val: int) -> int:k = 0for x in nums:if x != val:nums[k] = xk += 1return k
2,Java
class Solution {public int removeElement(int[] nums, int val) {int k = 0;for (int x : nums) {if (x != val) {nums[k++] = x;}}return k;}
}
你可以将上述代码复制到 LeetCode 编辑器中,结合题目示例进行测试。
五、总结与拓展
这道题的核心是 “用变量 k 追踪有效元素的位置和个数”,本质是双指针思想的简化 —— 如果把k看作 “慢指针”,遍历数组的索引(或元素)看作 “快指针”,就是典型的 “快慢双指针” 解法:快指针负责遍历数组筛选元素,慢指针负责存放有效元素。
类似题目拓展
这种 “双指针筛选元素” 的思路,在很多数组题目中都有应用,例如:
- 26. 删除有序数组中的重复项:用慢指针记录不重复元素的位置,快指针遍历数组,遇到不重复元素就放到慢指针位置,最后返回慢指针的值;
- 283. 移动零:用慢指针记录非零元素的位置,快指针遍历数组,将非零元素放到慢指针位置,遍历结束后将慢指针及之后的元素设为 0。
掌握这种思路,能帮你快速解决一系列 “原地筛选、原地修改” 的数组问题,提升解题效率。
希望通过本文的讲解,你能不仅学会 “移除元素” 这道题的解法,更能理解背后的双指针思想,做到举一反三,应对更多类似题目。