欢迎光临
我们一直在努力

移除元素(Remove Element)题目详解:三种解法与优化实践

题目描述

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

要求:

  • 原地修改数组,不能使用额外的数组空间
  • 元素的顺序可以改变
  • 返回不等于 val 的元素数量 k
  • 修改后的数组前 k 个元素必须是不等于 val 的元素
  • 数组的剩余元素和数组大小不重要
  • 示例:

    输入:nums = [3,2,2,3], val = 3
    输出:2, nums = [2,2,_,_]
    解释:函数应返回长度 2,且 nums 的前两个元素均为 2


    解法一:暴力删除法(时间复杂度 O(N²))

    核心思路

    遍历数组查找 val,找到后删除该位置元素,后续元素前移。

    代码实现

    int removeElement(int* nums, int numsSize, int val) {
    int size = numsSize;
    for (int i = 0; i < size; i++) {
    if (nums[i] == val) { // 找到要删除的元素
    // 将后续元素整体向前移动一位
    for (int j = i + 1; j < size; j++) {
    nums[j 1] = nums[j];
    }
    i; // 因为下标i以后的元素都向前移动了一位,所以i要减1
    size; // 数组大小减1
    }
    }
    return size;
    }

    复杂度分析

    • 时间复杂度:O(N²),最坏情况下每个元素都需要移动
    • 空间复杂度:O(1),原地修改

    适用场景

    • 小规模数据
    • 对时间复杂度要求不高的场景

    解法二:辅助数组法(时间复杂度 O(N),空间复杂度 O(N))

    核心思路

    创建临时数组存储非 val 元素,再拷贝回原数组。

    代码实现

    int removeElement(int* nums, int numsSize, int val) {
    int* tmp = (int*)malloc(sizeof(int) * numsSize); // 创建临时数组
    int k = 0; // 新数组索引

    // 遍历原数组,将非val元素存入临时数组
    for (int i = 0; i < numsSize; i++) {
    if (nums[i] != val) {
    tmp[k++] = nums[i];
    }
    }

    // 将临时数组拷贝回原数组
    for (int i = 0; i < k; i++) {
    nums[i] = tmp[i];
    }

    free(tmp); // 释放临时数组
    return k;
    }

    复杂度分析

    • 时间复杂度:O(N),遍历两次数组
    • 空间复杂度:O(N),需要额外数组空间

    适用场景

    • 允许使用额外空间
    • 需要保持元素原始顺序

    在这里插入图片描述


    解法三:双指针法(最优解)

    核心思路

    使用两个指针:

    • src(源指针):遍历原数组
    • dst(目标指针):指向下一个有效元素应该存放的位置

    算法流程

  • 初始化 src = 0, dst = 0
  • 遍历数组:
    • 如果 nums[src] != val:将 nums[src] 赋值给 nums[dst],然后 dst++
    • 无论是否相等,src 都向前移动
  • 返回 dst 作为新数组长度
  • 可视化过程

    初始:nums = [3,2,2,3], val = 3
    dst=0, src=0

    步骤1:nums[0]=3 == val → src=1, dst=0
    步骤2:nums[1]=2 != val → nums[0]=2, dst=1, src=2
    步骤3:nums[2]=2 != val → nums[1]=2, dst=2, src=3
    步骤4:nums[3]=3 == val → src=4

    结束:返回 dst=2, nums = [2,2,_,_]

    代码实现

    int removeElement(int* nums, int numsSize, int val) {
    int dst = 0; // 目标指针:指向下一个有效元素位置
    int src = 0; // 源指针:遍历原数组

    while (src < numsSize) {
    if (nums[src] != val) {
    // 将有效元素移动到前面
    nums[dst] = nums[src];
    dst++;
    }
    src++; // 源指针始终向前移动
    }

    return dst; // dst就是新数组的长度
    }

    优化版本(减少赋值操作)

    int removeElement(int* nums, int numsSize, int val) {
    int left = 0;
    int right = numsSize 1;

    while (left <= right) {
    if (nums[left] == val) {
    // 将val交换到数组末尾
    nums[left] = nums[right];
    right;
    } else {
    left++;
    }
    }
    return left;
    }

    复杂度分析

    • 时间复杂度:O(N),只需遍历一次数组
    • 空间复杂度:O(1),原地修改

    在这里插入图片描述


    三种解法对比

    解法时间复杂度空间复杂度是否原地保持顺序适用场景
    暴力删除 O(N²) O(1) 数据量小
    辅助数组 O(N) O(N) 允许额外空间
    双指针 O(N) O(1) 最优解

    关键经验总结

    1. 双指针法的核心思想

    • 快慢指针:一个指针遍历,一个指针记录有效位置
    • 覆盖而非删除:通过覆盖无效元素实现"删除"效果
    • 原地操作:避免额外空间开销

    2. 边界条件处理

    • 空数组:直接返回0
    • 全部元素都是val:返回0
    • 没有val元素:返回原数组长度

    3. 代码优化技巧

    // 技巧1:简化判断逻辑
    while (src < numsSize) {
    if (nums[src] != val) {
    nums[dst++] = nums[src]; // 合并赋值和自增
    }
    src++;
    }

    // 技巧2:使用for循环更简洁
    for (int src = 0; src < numsSize; src++) {
    if (nums[src] != val) {
    nums[dst++] = nums[src];
    }
    }

    4. 测试用例设计

    // 测试用例1:正常情况
    int nums1[] = {3,2,2,3};
    assert(removeElement(nums1, 4, 3) == 2);

    // 测试用例2:全部为val
    int nums2[] = {1,1,1,1};
    assert(removeElement(nums2, 4, 1) == 0);

    // 测试用例3:没有val
    int nums3[] = {4,5,6,7};
    assert(removeElement(nums3, 4, 8) == 4);

    // 测试用例4:空数组
    assert(removeElement(NULL, 0, 5) == 0);


    5. 力扣提交要点

  • 仔细阅读题目要求:明确返回值和数组修改要求
  • 考虑边界情况:空数组、全匹配、无匹配
  • 选择最优算法:双指针法是标准答案
  • 变量命名清晰:使用src/dst或fast/slow等有意义的名字
  • 添加必要注释:说明算法思路
  • 扩展思考

    1. 如果要求保持元素原始顺序?

    双指针法天然保持顺序,因为是从左到右遍历和覆盖。

    2. 如果val出现频率很高?

    优化版本的双指针(交换法)效率更高,减少赋值次数。

    3. 实际应用场景

    • 内存清理:移除特定值的内存块
    • 数据过滤:过滤掉无效或异常数据
    • 缓存管理:淘汰特定类型的缓存项

    结语

    移除元素问题虽然简单,但体现了算法设计的核心思想:

  • 空间换时间 vs 时间换空间的权衡
  • 双指针是处理数组问题的利器
  • 原地修改是面试中的常见要求
  • 掌握双指针法的思想,不仅能解决本题,还能应对:

    • 删除排序数组中的重复项
    • 移动零
    • 合并两个有序数组
      a- 等类似问题

    建议读者在理解基础上,自己手写代码实现,并尝试不同的测试用例,加深对双指针法的理解。

    赞(0)
    未经允许不得转载:171主机测评 » 移除元素(Remove Element)题目详解:三种解法与优化实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址