前言
作为算法入门的经典题型,LeetCode 27.移除元素看似简单,却藏着数组操作、双指针思想、边界判断等核心考点。很多新手(包括我)第一次写代码时都会踩逻辑坑,
一、题目再解读
原题题干
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变,不需要考虑数组中超出新长度后面的元素。
核心规则(抓重点)
原地修改:不能新建数组,只能在原数组上改,空间复杂度O(1)
返回值:返回移除后的数组长度k,原数组前k个元素为有效元素
顺序无关:有效元素顺序可以打乱,这是双指针优化的关键
无需处理k之后的元素,系统不会校验 示例演示
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,,]
解释:返回新长度2,数组前两位为有效元素,后续元素可忽略
二、新手踩坑实录(我的错误代码复盘)
第一次写这道题时,我想用首尾双指针思路,但因为逻辑判断失误,写出了有bug的代码,先复盘错误点,避免大家踩同样的坑。 错误代码展示
var removeElement = function (nums, val) {
//创建左右指针
let left = 0;
let right = nums.length - 1;
//计数k
let k = 0;
while((left<=nums.length-1)||(left<=right)){
// 致命错误:判断下标而非数组元素
if(left===val){
if(nums[right]!=val){
nums[left]=nums[right];
nums[right]=val;
left++;right--;k++;
}else{
right--;
}
}else{
left++
k++;
}
}
return k;
}
核心错误分析
1.判断条件写错:本意判断nums[left] === val,却写成left === val,混淆了数组下标和数组元素
2.循环条件冗余:只需left <= right即可,多余的判断会导致死循环或越界
3.计数逻辑混乱:双指针解法无需单独计数k,最终left下标就是有效长度,强行计数会导致结果偏差
三、最优解法:首尾双指针法(空间O(1),时间O(n))
解法思路
利用首尾双指针,从数组两端向中间遍历,核心逻辑:
左指针left:从头向后找等于val的待删除元素
右指针right:从尾向前找不等于val的有效元素
找到待删除元素时,用右指针指向的有效元素覆盖左指针位置,再收缩右指针
若当前元素有效,直接移动左指针
循环结束后,left下标即为有效数组长度
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
// 左指针:寻找待删除元素
let left = 0;
// 右指针:寻找有效元素
let right = nums.length - 1;
// 双指针相遇时结束循环
while (left <= right) {
// 找到待删除元素,用右侧有效元素覆盖
if (nums[left] === val) {
nums[left] = nums[right];
// 右指针左移,舍弃原右侧位置
right--;
} else {
// 当前元素有效,左指针继续前进
left++;
}
}
// 左指针下标即为有效数组长度
return left;
};
初始化指针:left从0开始(数组头部),right从nums.length-1开始(数组尾部)
循环条件:left <= right,保证双指针覆盖所有元素
核心判断:若左指针元素是val,用右指针元素覆盖,右指针左移(相当于删除当前元素)
有效元素:若左指针元素不是val,直接右移左指针,保留该元素
返回值:left最终停在有效元素末尾,即为新长度
四、复杂度分析
时间复杂度:O(n),数组仅遍历一次,双指针移动总次数不超过n
空间复杂度:O(1),仅用了两个指针变量,无额外数组空间开销,完全符合题目要求
五、举一反三:其他解法(快慢指针)
除了首尾双指针,这道题还能用快慢指针(同向双指针)解决,适合需要保留元素顺序的场景:
快指针:遍历整个数组,寻找有效元素
慢指针:记录有效元素的存放位置
快指针找到有效元素,就赋值给慢指针位置,慢指针右移
var removeElement = function(nums, val) {
let slow = 0;
for(let fast = 0; fast < nums.length; fast++){
if(nums[fast] !== val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
};
结语
算法入门不要怕写错题,复盘踩坑过程反而比直接记答案更有效。移除元素这道题吃透双指针思路后,类似的数组去重、元素移动题型都能套用这个逻辑。