React Router

009-移除元素 (Remove Element)

LeetCode 第27题 - 移除元素的详细题解

题目描述

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

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

解题思路

使用双指针法,一个指针 slow 指向当前要保留的元素位置,另一个指针 fast 遍历数组。当 fast 指向的元素不等于 val 时,将其移动到 slow 位置,然后 slow 向前移动。

JavaScript 解决方案

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
const 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;
};

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组长度
  • 空间复杂度:O(1),只使用了常数额外空间

测试用例

// 测试函数
const testRemoveElement = () => {
    const testCases = [
        {
            nums: [3, 2, 2, 3],
            val: 3,
            expected: 2,
            expectedArray: [2, 2]
        },
        {
            nums: [0, 1, 2, 2, 3, 0, 4, 2],
            val: 2,
            expected: 5,
            expectedArray: [0, 1, 3, 0, 4]
        },
        {
            nums: [1, 2, 3, 4],
            val: 5,
            expected: 4,
            expectedArray: [1, 2, 3, 4]
        },
        {
            nums: [1, 1, 1, 1],
            val: 1,
            expected: 0,
            expectedArray: []
        },
        {
            nums: [],
            val: 1,
            expected: 0,
            expectedArray: []
        },
        {
            nums: [1],
            val: 1,
            expected: 0,
            expectedArray: []
        },
        {
            nums: [1],
            val: 2,
            expected: 1,
            expectedArray: [1]
        }
    ];

    testCases.forEach((testCase, index) => {
        const nums = [...testCase.nums]; // 复制数组避免修改原数组
        const result = removeElement(nums, testCase.val);
        const resultArray = nums.slice(0, result);
        const passed = result === testCase.expected && 
                      JSON.stringify(resultArray.sort()) === JSON.stringify(testCase.expectedArray.sort());
        
        console.log(`测试用例 ${index + 1}: ${passed ? '通过' : '失败'}`);
        console.log(`输入: nums = [${testCase.nums}], val = ${testCase.val}`);
        console.log(`期望: ${testCase.expected}, nums = [${testCase.expectedArray}]`);
        console.log(`实际: ${result}, nums = [${resultArray}]\n`);
    });
};

// 运行测试
testRemoveElement();

关键点总结

  1. 双指针法:使用快慢指针来原地修改数组
  2. 原地操作:不创建新数组,直接在原数组上操作
  3. 元素顺序:题目允许改变元素顺序
  4. 边界处理:空数组、单个元素、全部移除等特殊情况
  5. 返回值:返回移除后数组的新长度

总结

这道题考察了以下重点:

  • 双指针技巧:理解快慢指针在数组操作中的应用
  • 原地算法:如何在不使用额外空间的情况下修改数组
  • 数组遍历:高效地遍历和处理数组元素
  • 边界处理:对各种边界情况的考虑

双指针法是最优解,时间复杂度为 O(n),空间复杂度为 O(1)。在实际面试中,建议先解释双指针的思路,然后实现算法。