【新手必看】「移除元素」超详细解析:双指针技巧原来这么简单!

322 阅读3分钟

一、为什么"移除元素"是入门必刷题?

作为算法新手,我第一次看到LeetCode第27题"移除元素"时,内心是崩溃的。题目要求原地修改数组,还不能用额外空间——这简直是把路都堵死了啊!但后来发现,这道题其实是学习双指针技巧的绝佳案例。

先看题目要求:

给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回新数组的长度。

举个🌰:

输入:nums = [3,2,2,3], val = 3
输出:2 → 修改后的数组应为 [2,2,_,_]

二、从暴力解法开始思考

方法1:直接删除法(错误示范❌)

新手最容易想到的就是用数组的splice方法:

function removeElement(nums, val) {
    for(let i=0; i<nums.length; i++){
        if(nums[i] === val){
            nums.splice(i,1)
            i-- // 必须回退!
        }
    }
    return nums.length
}

问题:每次删除元素都会改变数组长度,导致索引错乱。虽然通过i--能解决,但时间复杂度高达O(n²),性能极差。


方法2:新建数组法(不符合要求❌)

function removeElement(nums, val) {
    let newArr = []
    for(let num of nums){
        if(num !== val) newArr.push(num)
    }
    return newArr.length
}

问题:题目明确要求原地修改,这种方法虽然简单但直接犯规!


三、双指针法:优雅的解决方案 ✅

1. 快慢指针思想

想象有两个指针:

  • 快指针:侦察兵,负责遍历所有元素
  • 慢指针:建筑师,只记录保留的元素
function removeElement(nums, val) {
    let slow = 0
    for(let fast=0; fast<nums.length; fast++){
        if(nums[fast] !== val){
            nums[slow] = nums[fast]
            slow++
        }
    }
    return slow
}

2. 动图演示

初始状态:[3,2,2,3], val=3
快指针↑  慢指针↑
第1步:快=3 → 跳过
第2步:快=2 → 复制到慢指针位置 [2,2,2,3], 慢+1
第3步:快=2 → 复制 [2,2,2,3], 慢+1
第4步:快=3 → 跳过
最终返回 slow=2,数组前两位是有效元素

3. 为什么高效?

  • 时间复杂度:O(n) → 只遍历一次
  • 空间复杂度:O(1) → 只用了常数空间

四、变式训练:26题(去重)

学会了27题,26题"删除有序数组中的重复项"就很简单了:

function removeDuplicates(nums) {
    let slow = 1
    for(let fast=1; fast<nums.length; fast++){
        if(nums[fast] !== nums[fast-1]){
            nums[slow] = nums[fast]
            slow++
        }
    }
    return slow
}

技巧:比较fastfast-1,而不是fastslow


五、避坑指南

  1. 边界条件:空数组怎么办?
    if(nums.length === 0) return 0
    
  2. 元素顺序:题目没要求保持顺序时,可以用首尾指针法更高效:
    while(left <= right){
        if(nums[left] === val){
            nums[left] = nums[right]
            right--
        }else{
            left++
        }
    }
    

六、总结

核心思想
双指针就像两个人配合工作——一个负责筛选(快指针),一个负责记录(慢指针)。这种模式还能解决:

  • 移动零(283题)
  • 比较含退格的字符串(844题)
  • 链表中删除节点

新手建议

  1. 先写暴力解法,再优化
  2. 多用console.log调试
  3. 画图辅助理解指针移动

(附:文中所有代码已测试通过,可直接复制到LeetCode运行)