
示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本篇技术指南以开源仓库 doocs/leetcode 中 0027.Remove Element 题解文档 为核心完整讲解 LeetCode 27「移除元素」的题目机制、评测逻辑与最优解法并结合仓库中 Java、Python、C、Go、TypeScript、Rust、JavaScript、C#、PHP 共 9 种语言的源码实现剖析「一次遍历覆盖法」的底层原理。读完本文你将掌握原地数组改写In-place的核心套路——用写指针覆盖前部、以返回值代替真实删除并能举一反三应用到去重、移动零等同类双指针问题。一、题目回顾移除元素到底要做什么题目给出一个整数数组nums和一个值val要求原地In-place移除所有数值等于val的元素。注意题目明确说明元素的顺序可能发生改变然后返回nums中与val不同的元素的数量。设nums中不等于val的元素数量为k要通过此题需要执行以下操作更改nums数组使nums的前k个元素包含不等于val的元素nums的其余元素和nums的大小并不重要。返回k。约束条件来自原题文档0 nums.length 1000 nums[i] 500 val 100示例 1输入nums [3,2,2,3], val 3 输出2, nums [2,2,_,_] 解释你的函数应该返回 k 2, 并且 nums 中的前两个元素均为 2。 你在返回的 k 个元素之外留下了什么并不重要因此它们并不计入评测。示例 2输入nums [0,1,2,2,3,0,4,2], val 2 输出5, nums [0,1,4,0,3,_,_,_] 解释你的函数应该返回 k 5并且 nums 中的前五个元素为 0,0,1,3,4。 注意这五个元素可以任意顺序返回。 你在返回的 k 个元素之外留下了什么并不重要因此它们并不计入评测。注意示例 2 中前五个元素是[0,1,4,0,3]与输入中[0,1,2,2,3,0,4,2]的顺序并不完全一致0与4的相对位置发生了交换这印证了元素的顺序可能发生改变这一关键条件。二、评测机制判定标准决定了解法方向这道题自带一个自定义评测机Custom Judge其判定逻辑直接决定了我们写代码的方式。原题文档给出了评测机使用的测试代码int[] nums [...]; // 输入数组 int val ...; // 要移除的值 int[] expectedNums [...]; // 长度正确的预期答案。 // 它以不等于 val 的值排序。 int k removeElement(nums, val); // 调用你的实现 assert k expectedNums.length; sort(nums, 0, k); // 排序 nums 的前 k 个元素 for (int i 0; i k; i) { assert nums[i] expectedNums[i]; }从这段评测代码可以提炼出三条关键信息只检查前k个位置评测机只对nums[0..k-1]进行排序并逐一断言k之后的内容完全被忽略。这意味着我们**根本不需要真正删除**数组尾部元素只要把该保留的值搬运到前部即可。相对顺序可以改变评测机在断言前会对前k个元素调用sort因此返回值与预期值不必保持输入时的相对顺序。这是这道题与第 26 题「删除有序数组中的重复项」的本质区别——26 题要求保留有序数组的相对顺序而本题允许任意顺序。必须原地修改评测机直接读取修改后的nums因此不能简单地返回一个新建数组的长度。理解了这三点如何以最低成本通过评测就有了明确的优化方向。三、三种思路的对比为什么最终选择覆盖法原题文档的思考部分对三种思路做了权衡这里展开说明思路一另开一个数组拷贝所有不等于val的元素时间复杂度为O(n)当n 100时完全可以通过评测。但题目明确要求原地改写额外的数组分配并不必要还会让空间复杂度变成O(n)与本题的考察意图原地算法相悖。思路二每次删除时把后面的元素整体前移模拟erase如果采用找到等于val的元素就把它删掉、后面的元素逐个前移的朴素做法最坏情况下每次前移都要搬运O(n)个元素整体退化为O(n²)的时间复杂度。虽然n 100时也能跑完但这不是最优解也偏离了出题人想考察的算法素养。思路三一次遍历覆盖法本题标准解题目只要求前k个位置覆盖全部非val元素且顺序可以改变。因此不必真正删除用k记录已写下的个数遇到x ! val就写入nums[k]并将k加一。一遍扫描即可完成额外空间为O(1)。三种思路的量化对比思路时间复杂度空间复杂度是否满足原地要求另开数组拷贝O(n)O(n)否逐次删除前移O(n²)O(1)是一次遍历覆盖O(n)O(1)是四、一次遍历覆盖法原理、正确性与边界4.1 算法流程用变量k记录当前不等于val的元素个数它同时充当写指针遍历数组nums读指针即循环变量如果当前元素x不等于val则将x赋值给nums[k]并将k自增1遍历结束后返回k。4.2 不变式与正确性算法的核心不变式invariant是在循环的任意时刻nums[0..k-1]已经保存了扫描过的所有不等于val的元素且k等于这些元素的个数。证明思路初始k 0前缀为空不变式成立归纳当读指针扫到元素x时若x ! val把它写入nums[k]并k前缀仍恰好包含全部已扫描的非val元素若x val跳过且k不变前缀不受影响终止扫描完整数组后nums[0..k-1]恰好包含全部非val元素即k就是要返回的答案。由于写指针k永远不大于读指针写指针只有在写入时才前进且每次写入位置k都已被读指针扫描过覆盖不会破坏尚未读到的数据这是该算法不需要额外数组的前提。4.3 边界情况空数组nums.length 0时循环体不执行直接返回0正确。val不存在每个元素都满足x ! valnums[k]原地自覆盖写入位置与读出位置相同返回原数组长度正确。全等于valk始终为0不产生任何写入返回0评测机只检查空的前缀正确。五、多语言实现doocs/leetcode 仓库源码全览doocs/leetcode 仓库为本题提供了 9 种语言的实现README 中的代码与目录下独立 Solution 文件一一对应如 Solution.java、Solution.py、Solution.rs 等可直接复制运行。以下完整给出各语言实现。Python3Solution.pyclass Solution: def removeElement(self, nums: List[int], val: int) - int: k 0 for x in nums: if x ! val: nums[k] x k 1 return kJavaSolution.javaclass Solution { public int removeElement(int[] nums, int val) { int k 0; for (int x : nums) { if (x ! val) { nums[k] x; } } return k; } }CSolution.cppclass Solution { public: int removeElement(vectorint nums, int val) { int k 0; for (int x : nums) { if (x ! val) { nums[k] x; } } return k; } };GoSolution.gofunc removeElement(nums []int, val int) int { k : 0 for _, x : range nums { if x ! val { nums[k] x k } } return k }TypeScriptSolution.tsfunction removeElement(nums: number[], val: number): number { let k: number 0; for (const x of nums) { if (x ! val) { nums[k] x; } } return k; }RustSolution.rsimpl Solution { pub fn remove_element(nums: mut Veci32, val: i32) - i32 { let mut k 0; for i in 0..nums.len() { if nums[i] ! val { nums[k] nums[i]; k 1; } } k as i32 } }JavaScriptSolution.js/** * param {number[]} nums * param {number} val * return {number} */ var removeElement function (nums, val) { let k 0; for (const x of nums) { if (x ! val) { nums[k] x; } } return k; };C#Solution.cspublic class Solution { public int RemoveElement(int[] nums, int val) { int k 0; foreach (int x in nums) { if (x ! val) { nums[k] x; } } return k; } }PHPSolution.phpclass Solution { /** * param Integer[] $nums * param Integer $val * return Integer */ function removeElement($nums, $val) { $k 0; foreach ($nums as $x) { if ($x ! $val) { $nums[$k] $x; } } return $k; } }5.1 各语言实现的共性观察逻辑完全同构9 种语言都是写指针k 读指针循环变量的结构证明该算法的语言无关性适合作为双指针模板记忆签名差异源于语言约定PHP 通过$nums显式按引用传参、Rust 使用mut Veci32可变借用都是对原地修改这一要求的语言级表达C 使用vectorint引用传参同理返回类型统一所有实现都返回int/i32与题目要求的返回 k一致。六、复杂度分析与延伸思考6.1 复杂度结论时间复杂度 O(n)对数组做一次线性扫描每个元素只访问一次空间复杂度 O(1)只使用常数个变量k与循环变量不依赖数组长度。其中n为数组nums的长度。这是该问题可达的最优复杂度因为至少要扫描一遍数组才能知道哪些元素需要保留。6.2 从移除元素到双指针家族理解了 27 题可以顺藤摸瓜掌握一系列双指针数组题这些在 doocs/leetcode 仓库中均有对应题解26. 删除有序数组中的重复项题解同样是前 k 个位置保留有效值的覆盖思路但要求保留相对顺序且只保留不重复元素写指针的比较对象从val变为上一个已写入的值283. 移动零把保留非零值、其余补零等价转化为先覆盖非零值再对尾部补 080. 删除有序数组中的重复项 II放宽为每个元素最多出现两次写指针需要参考前两位已写入的值。这些题目共享同一个思维模型用一个慢指针维护结果前缀用一个快指针扫描原数组27 题是其中最简单的入门样例。七、在 doocs/leetcode 仓库中查阅与进一步学习本题在仓库中的完整目录为 solution/0000-0099/0027.Remove Element/包含README.md中文题解文档题目描述、评测机制、解法与多语言代码README_EN.md英文版题解文档Solution.*9 种语言的独立可提交源码文件。仓库按题号区间组织题解如0000-0099、0100-0199……每道题一个目录solution/README.md 提供全部题目的索引表可在其中快速检索题号、题解、标签与难度。此外仓库还收录了《剑指 Offer第 2 版》lcof/README.md、《程序员面试金典第 6 版》lcci/README.md等系列题解其中同样包含大量双指针、数组类题目可与本题对照学习。小结LeetCode 27「移除元素」的价值不在题目本身而在它揭示的原地算法范式当评测只关心结果前缀且顺序可以改变时用慢指针覆盖 快指针扫描即可在 O(n) 时间、O(1) 空间内完成任务。doocs/leetcode 仓库以 9 种语言完整呈现了这一解法既可作为复习模板也是理解后续所有前缀覆盖类双指针问题的起点。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐LogicStack-LeetCode 刷穿系列精讲LeetCode 27 移除元素 —— 双指针与「保留逻辑」通解LogicStack LeetCode 刷穿系列精讲LeetCode 27 移除元素 —— 双指针与「保留逻辑」通解 本文以「宫水三叶的刷题日记」系列仓库 L教程文档LogicStack-LeetCode 刷题笔记双指针与通用解法吃透数组移除元素问题LeetCode 26 / 27LogicStack LeetCode 刷题笔记双指针与通用解法吃透数组移除元素问题LeetCode 26 / 27 本篇技术指南围绕公众号「宫水三叶的刷教程文档doocs/leetcode 算法精解面试题 02.02 返回倒数第 k 个节点——双指针一次遍历求单向链表倒数第 k 个元素doocs/leetcode 算法精解面试题 02.02 返回倒数第 k 个节点——双指针一次遍历求单向链表倒数第 k 个元素 导读 本文基于 doocs/示例工程上一篇实时系统编程终极指南lowlevelprogramming-university项目实战解析下一篇llama2-webui CPU部署优化让普通电脑也能流畅运行大模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考