资讯动态

LeetCode 27. 移除元素(C)解题分析

发布时间:2026/10/16 3:16:46 来源:尧图企业网站定制
题目27. 移除元素语言C分析日期2026-10-14代码来源用户于 2026-10-14 在本对话中粘贴的函数user pasted结论你的代码能正确保留所有不等于val的元素并保持它们的相对顺序但它创建了int n[numsSize]额外空间为O(numsSize)不符合题目要求的O(1)额外空间。推荐使用单指针覆盖法。1. 题意和输入模型题目给出整数数组nums和整数val要求原地移除所有等于val的元素并返回不等于val的元素数量k。题目只检查修改后的nums[0..k-1]。k之后的内容可以是任意值不需要清零也不需要保持原样。项目含义nums[0..numsSize-1]输入数组的全部有效元素val需要移除的目标值返回值k保留元素的数量结果要求nums[0..k-1]中不能出现val顺序要求题面不强制稳定顺序保留相对顺序是更直观的选择额外空间O(1)这里必须区分两个概念物理数组容量numsSize 个 int 逻辑结果区间nums[0..k-1] ↑ 返回 k被“移除”的元素不需要真的从内存中消失。只要把保留元素写到数组前缀并返回前缀长度k就完成了题目定义的移除。边界情况numsSize 0返回0。所有元素都等于val返回0。没有元素等于val返回numsSize数组可以保持不变。val可以是负数、0或正数不能把某个数值当作空位哨兵。numsSize 1仍然可以直接处理。2. 你的实现int removeElement(int* nums, int numsSize, int val) { int n[numsSize]; int p 0; for (int i 0; i numsSize; i) { if (nums[i] ! val) { n[p] nums[i]; p; } } for (int j 0; j p; j) { nums[j] n[j]; } return p; }3. 逐分支检查3.1 正确的部分第一轮扫描中if (nums[i] ! val) { n[p] nums[i]; p; }循环结束时n[0..p-1]恰好保存了所有不等于val的元素且顺序与原数组一致。第二轮for (int j 0; j p; j) { nums[j] n[j]; }把这些保留元素复制回nums的前缀因此返回p后nums[0..p-1]满足题目要求。你的实现不会把合法的0、负数或其他值误判为空位也没有修改val或引入排序因此在结果正确性上没有发现反例。3.2 主要问题额外空间违反题目约束int n[numsSize];这是一个 C99 变长数组VLA需要为最多numsSize个int分配栈空间。因此项目你的实现时间复杂度O(numsSize)额外空间O(numsSize)是否原地结果写回nums但使用了线性辅助数组题目约束不满足O(1)额外空间这不是仅影响性能的细节而是题目明确要求的空间复杂度不合规。在线评测可能仍然接受某些测试规模下的代码但算法分析和提交质量都应判定为不符合进阶要求。3.3 C 语言和可移植性风险int n[numsSize]还有两个需要注意的 C 语言问题VLA 不是所有编译器都支持。VLA 从 C99 开始被标准化但在 C11 以后属于可选特性MSVC 的传统 C 编译器也不按 GCC/Clang 的方式支持它。若题目环境使用支持 VLA 的编译器代码可能正常编译作为通用 C 代码不能假设所有编译器都接受。numsSize 0时不能依赖零长度数组。题目允许空数组。int n[0]不是标准 C 中可移植的数组声明某些编译器会把它作为扩展接受另一些编译器会报错或给出诊断。即使当前平台放行也没有必要为这个边界引入风险。如果改成malloc虽然能避免栈上 VLA但额外空间仍然是O(numsSize)并没有满足本题的O(1)要求。3.4 不必要的两阶段复制你的代码先把保留元素写入n再从n复制回nums。这相当于每个保留元素最多写两次。可以直接使用p作为nums的写指针读取指针 i依次查看原数组元素 写入指针 p写入下一个保留元素 当 nums[i] val跳过 当 nums[i] ! valnums[p] nums[i]p由于p不会大于i写入位置不会超过当前读取位置不会覆盖尚未读取的元素。4. 推荐算法双指针覆盖法flowchart LR A[初始化 p 0] -- B{扫描 i 0 到 numsSize - 1} B -- C{nums[i] val?} C -- 是 -- D[跳过当前元素] C -- 否 -- E[nums[p] nums[i]; p] D -- B E -- B B --|扫描结束| F[返回 p]推荐实现int removeElement(int* nums, int numsSize, int val) { int p 0; for (int i 0; i numsSize; i) { if (nums[i] ! val) { nums[p] nums[i]; p; } } return p; }循环不变量和正确性证明设循环处理到下标i之前nums[0..p-1]恰好保存原数组nums[0..i-1]中所有不等于val的元素这些元素的相对顺序保持不变0 p i。初始化时i 0、p 0结果前缀为空不变量成立。每轮处理nums[i]若nums[i] val它不应进入结果p不变原有结果前缀仍正确。若nums[i] ! val将它写入nums[p]并令p。由于p i写入位置不会超过当前读取位置尚未读取的区间不会被覆盖结果前缀继续按原顺序扩展。扫描结束后nums[0..p-1]就是原数组中所有不等于val的元素返回p正确。原数组 [3, 2, 2, 3] val 3 i 0, nums[i] 3 - 跳过p 0 i 1, nums[i] 2 - nums[0] 2p 1 i 2, nums[i] 2 - nums[1] 2p 2 i 3, nums[i] 3 - 跳过p 2 结果前缀 [2, 2] 返回25. 复杂度比较方案时间复杂度额外空间顺序结论你的实现辅助 VLA 回写O(numsSize)O(numsSize)保持结果正确但不满足空间约束推荐单指针覆盖O(numsSize)O(1)保持满足题目要求可选交换到末尾O(numsSize)O(1)可能改变适合不要求稳定顺序时减少写入本题还可以利用“顺序不作要求”做一个交换优化遇到val时用数组末尾尚未处理的元素替换它并缩短有效区间。这种做法可能减少写入次数但控制流更复杂也可能改变保留元素的相对顺序。对于学习和面试表达稳定的单指针覆盖法更清晰。6. 找到val后从末尾交换的可选版本如果明确不需要保持剩余元素的相对顺序可以使用int removeElement(int* nums, int numsSize, int val) { int k numsSize; int i 0; while (i k) { if (nums[i] val) { nums[i] nums[k - 1]; --k; } else { i; } } return k; }这里替换进来的元素必须重新检查所以命中val时不递增i。例如nums [2, 3, 3, 2], val 3 i 1, k 4 用 nums[3] 2 替换 nums[1]k 3 此时 nums[1] 仍需检查但它是 2这段代码同样是O(n)时间、O(1)空间但稳定性较弱。若题目或后续代码依赖保留元素顺序应使用单指针覆盖法。7. 回归测试矩阵下面的测试只比较返回值k和nums[0..k-1]因为题目明确允许忽略k之后的内容。输入numsval期望k期望有效前缀覆盖点[3,2,2,3]32[2,2]常规示例[0,1,2,2,3,0,4,2]25[0,1,3,0,4]0是合法值重复目标值[]10[]空数组[1]10[]单元素且被移除[1]21[1]单元素且保留[2,2,2]20[]全部移除[1,2,3]43[1,2,3]没有目标值[-2,-1,0,1]-23[-1,0,1]负数目标值[5,5,1,5,2]52[1,2]目标值在开头、中间[1,1,2,1,3]12[2,3]非连续保留元素8. 分项结论检查项结论类型保留元素判断nums[i] ! val正确正确性保留元素相对顺序正确保持正确选择返回保留数量p正确正确性0、负数等合法值没有误用哨兵正确性int n[numsSize]线性额外空间题目约束问题numsSize 0VLA 存在可移植性风险C/可移植性C 编译器支持依赖 VLA 支持可移植性整数溢出无算术运算未发现溢出风险安全性时间复杂度O(n)合格9. 最终建议你的代码的筛选逻辑是正确的但不应使用int n[numsSize]保存完整结果。将辅助数组去掉让p直接成为nums内的写指针就能在保持相对顺序的同时把额外空间降为O(1)int removeElement(int* nums, int numsSize, int val) { int p 0; for (int i 0; i numsSize; i) { if (nums[i] ! val) { nums[p] nums[i]; } } return p; }这也是本题最值得掌握的抽象数组题中的“删除”通常不是缩短内存而是用写指针构造符合条件的有效前缀并返回该前缀长度。10. 来源与证据限制LeetCode CN 27. 移除元素题目定义、原地修改语义、返回k的有效前缀语义、数组长度和值域约束。公开页面访问日期2026-10-14。LeetCode CN 27. 移除元素canonical description 路径用于核对题面。公开页面访问日期2026-10-14。本次没有读取用户的 LeetCode 编辑器或提交状态个人代码来源仅为本对话中用户粘贴的函数。因此本文评价的是这段源码在题目约束下的静态行为不声称它已经在 LeetCode 提交并通过。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑