【LeetCode 27. 移除元素】C++ 范围 for 极简实现与原理解析

【LeetCode 27. 移除元素】C++ 范围 for 极简实现与原理解析

本文将详细讲解 LeetCode 第 27 题「移除元素」的高效解法,重点介绍如何使用 C++11 范围 for 循环实现原地修改,并深入分析其原理与性能优势。


一、题目回顾

给你一个数组 nums 和一个值 val,你需要原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

  • 要求:原地修改数组,使前 k 个元素为有效元素,其余元素无需关心。
  • 返回值:有效元素的数量 k

示例:

  • 输入:nums = [3,2,2,3], val = 3
  • 输出:2,数组前两位变为 [2,2]

二、核心思路:双指针法

这道题的经典解法是双指针法

  • 用一个慢指针 k 记录有效元素的存放位置
  • 遍历数组,遇到不等于 val 的元素,就将其写入 nums[k],并将 k 后移。
  • 遍历结束后,k 即为有效元素的数量,数组前 k 位就是结果。

为什么用范围 for?

传统写法需要手动管理索引,而 C++11 引入的范围 for 循环可以更简洁地遍历容器,代码可读性更高,且不易出错。在本题中,范围 for 可以完美替代传统索引遍历,实现极简代码。


三、代码实现(范围 for 版本)

cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int k = 0; // 慢指针,记录有效元素位置
        // 范围for遍历所有元素
        for (auto x : nums) {
            if (x != val) {
                nums[k++] = x; // 写入有效元素,指针后移
            }
        }
        return k; // 返回有效元素数量
    }
};

代码解析

  1. 参数传递vector<int>& nums 是引用传递,保证函数内修改直接作用于原数组。
  2. 慢指针 k:初始为 0,每次遇到有效元素就写入当前位置,然后自增。
  3. 范围 forfor (auto x : nums) 会自动遍历 nums 中的每个元素,x 是当前元素的拷贝(也可写 auto& x 避免拷贝,性能更优)。
  4. 原地修改 :将不等于 val 的元素依次写入数组前半部分,完成原地移除。
  5. 返回值k 即为有效元素的数量,调用者可通过 k 截取结果。

四、原理解析:为什么返回 k 就够了?

很多同学会疑惑:函数只返回了 k,为什么评测机能拿到正确数组?

核心原因有两点:

  1. 原地修改nums 是引用传递,函数内部已经把有效元素覆盖到了数组前 k 位。
  2. 约定返回值k 告诉调用者「有效元素有多少个,都存在数组前半部分」,评测机会根据 k 截取前 k 个元素与预期结果对比。

以示例 [3,2,2,3] 为例:

  • 遍历后数组变为 [2,2,3,3]k=2
  • 评测机截取前 2 个元素 [2,2],与预期一致,判定通过。

五、复杂度分析

  • 时间复杂度:O (n),仅遍历一次数组,所有操作均为常数级。
  • 空间复杂度:O (1),仅使用常数级额外空间,原地修改无额外内存开销。

性能优化:范围 for vs 传统 for

  • 范围 for 代码更简洁,可读性更高,适合遍历容器场景。
  • 底层实现与传统 for 一致,性能无差异,编译器会优化为等价的索引遍历。
  • 若需避免元素拷贝,可使用 for (auto& x : nums),效率更优。

六、拓展:首尾双指针优化(可选)

val 出现次数很少时,可使用首尾双指针进一步减少赋值操作:

cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int left = 0, right = nums.size();
        while (left < right) {
            if (nums[left] == val) {
                nums[left] = nums[right - 1];
                right--;
            } else {
                left++;
            }
        }
        return left;
    }
};
  • 思路:左指针找 val,右指针找非 val,交换两者,减少无效赋值。
  • 时间复杂度仍为 O (n),但赋值次数更少,适合 val 稀疏场景。

七、总结

  • 核心思想:双指针法实现原地移除,时间 / 空间复杂度最优。
  • 代码极简:范围 for 让代码更简洁易读,适合日常开发。
  • 关键理解 :返回 k 是有效元素计数,数组已在函数内原地修改,两者配合完成题目要求。

这道题是数组操作的基础入门题,掌握双指针思想和范围 for 的使用,对后续学习更复杂的数组 / 链表操作有很大帮助。

相关推荐
A923A2 小时前
【洛谷刷题 | 第九天】
算法·二分·洛谷
旖-旎2 小时前
位运算(只出现一次的数字|||)(5)
c++·算法·leetcode·位运算
AIpanda8882 小时前
数字员工是什么?熊猫智汇在提高销售转化率中的作用有哪些?
算法
源码之家2 小时前
计算机毕业设计:基于Python的美食数据采集可视化系统 Django框架 Scrapy爬虫 可视化 数据分析 大数据 机器学习 食物 食品(建议收藏)✅
python·算法·机器学习·信息可视化·课程设计
简简单单做算法2 小时前
基于Q-Learning强化学习的小车倒立摆平衡控制系统matlab性能仿真
算法·matlab·强化学习·qlearning·小车倒立摆平衡控制
kyle~2 小时前
ROS2---客户端服务(rclcpp::Client)
c++·物联网·机器人·ros2
Book思议-2 小时前
【数据结构】循环与递归:C 语言实现求和与斐波那契数列的两种思路
数据结构·算法·循环与递归
Mr_Xuhhh2 小时前
LeetCode 热题 100 刷题笔记:从数组到字符串的经典解法(续)
java·数据结构·算法
VelinX2 小时前
【个人学习||算法】贪心算法
学习·算法·贪心算法