当前位置: 首页 > news >正文

Leetcode 35

1 题目

26. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k。去重后,返回唯一元素的数量 k。

nums 的前 k 个元素应包含 排序后 的唯一数字。下标 k - 1 之后的剩余元素可以忽略。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 并且原数组 nums 的前两个元素被修改为 1, 2 不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4,_,_,_,_,_]
解释:函数应该返回新的长度 5 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 不需要考虑数组中超出新长度后面的元素。

提示:

1 <= nums.length <= 3 * 104
-100 <= nums[i] <= 100
nums 已按 非递减 顺序排列。

2 代码实现

int removeDuplicates(int* nums, int numsSize) {if (numsSize == 0){return 0;}int slow = 0 ;for ( int fast = 1 ; fast < numsSize ; fast ++){if(nums[fast] != nums [slow]){slow ++;nums[slow] = nums [fast];}}return slow +1;
}

第一次写的错误百出

int removeDuplicates(int* nums, int numsSize) {for(int i = 1 ; i < numsSize; i ++){for (int j = 0 ; j < numsSize - 1 ; j ++){if(nums[i]== nums[j]){nums [i] = nums [i+1] ;}}}return nums;
}

你提供的这段代码是想实现移除数组中重复元素的功能,但存在一些逻辑和语法问题,导致无法正确运行。以下是具体问题分析和修正方案:

问题分析

  1. 返回值错误函数声明返回类型为 int,但代码中 return nums; 试图返回数组指针,类型不匹配,会导致编译错误。通常这类函数需要返回去重后数组的新长度。

  2. 去重逻辑错误嵌套循环的逻辑存在问题:

    • 当发现 nums[i] == nums[j] 时,仅用 nums[i+1] 覆盖 nums[i],但未处理后续元素的移动,也没有减少数组长度,会导致重复元素无法真正被移除。
    • 循环边界 j < numsSize - 1 可能越界(当 i 是最后一个元素时,i+1 会超出原数组范围)。
  3. 未处理重复元素后的长度变化去重后数组长度会减小,但代码中始终使用原始 numsSize 作为循环边界,导致无效元素仍被处理。

修正方案(双指针法)

移除有序数组中的重复元素(假设题目是有序数组,否则需先排序),最优方法是用双指针

  • 慢指针 slow 指向去重后数组的末尾。
  • 快指针 fast 遍历整个数组,找到与 slow 指向不同的元素时,更新 slow 并赋值。
int removeDuplicates(int* nums, int numsSize) {if (numsSize == 0) { // 处理空数组特殊情况return 0;}int slow = 0; // 慢指针,指向去重后最后一个元素for (int fast = 1; fast < numsSize; fast++) {if (nums[fast] != nums[slow]) { // 找到不重复的元素slow++; // 慢指针前移nums[slow] = nums[fast]; // 覆盖重复位置}}return slow + 1; // 新长度为慢指针索引+1
}

说明

  • 该方法仅适用于有序数组(题目通常默认有序,如 LeetCode 第 26 题),如果数组无序,需先通过 qsort 排序再去重。
  • 时间复杂度 O(n),空间复杂度 O(1),效率远高于嵌套循环(嵌套循环时间复杂度 O(n²))。
  • 函数返回去重后的新长度,原数组前 slow+1 个元素即为去重后的结果。

3 题目

27. 移除元素

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

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

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 < actualLength; i++) {assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 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 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

4 代码实现

int removeElement(int* nums, int numsSize, int val) {int count = 0 ;for (int  i = 0 ; i < numsSize ; i ++){if(nums [i] != val){nums[count] = nums[i]; count++;}}return count;
}

错误百出

int removeElement(int* nums, int numsSize, int val) {int count = 0 ;for (int  i = 0 ; i < numsSize ; i ++){if(nums [i] == val){count++;nums[i] = nums[i + 1]; }i++;}return count;
}

你提供的这段代码用于移除数组中等于目标值val的元素,但存在一些逻辑问题,可能无法正确实现功能。以下是具体分析和修正建议:

问题分析

  1. 循环变量重复自增:代码中for循环已经包含i++,但循环体内又额外执行了i++,导致i每次循环实际增加 2,会跳过一半的元素,无法遍历整个数组。

  2. 元素覆盖错误:当遇到nums[i] == val时,直接用nums[i+1]覆盖nums[i],但未考虑:

    • i是最后一个元素(i = numsSize - 1),nums[i+1]会访问越界。
    • 覆盖后未重新检查当前位置(可能新覆盖的元素仍是val)。
  3. 返回值错误:题目要求返回 “移除后数组的新长度”,而代码返回的是 “被移除元素的个数”,逻辑相反。

修正方案(双指针法)

推荐使用双指针高效解决,时间复杂度O(n),空间复杂度O(1)

int removeElement(int* nums, int numsSize, int val) {int left = 0; // 慢指针:指向新数组的末尾for (int right = 0; right < numsSize; right++) {if (nums[right] != val) {// 若当前元素不等于val,放入新数组nums[left] = nums[right];left++; // 新数组长度+1}}return left; // left即为新数组的长度
}

修正说明

  • 双指针逻辑
    • right(快指针)遍历整个原数组,寻找不等于val的元素。
    • left(慢指针)记录新数组的有效长度,只在right找到有效元素时移动,并将元素覆盖到left位置。
  • 返回值left最终等于新数组中有效元素的个数(即移除后数组的长度)。
  • 优势:避免了重复遍历和越界问题,高效完成元素移除(覆盖式)。

例如,对于输入nums = [3,2,2,3], val = 3,修正后的代码会返回2,且nums前 2 个元素为[2,2],符合预期。

【我发现这种原地修改的循环约束条件好像通常是!=的条件下?】

你观察得很对!在原地修改数组的场景中(比如移除元素、筛选元素等),循环里确实经常用 != 作为核心判断条件,这本质上是由 “保留有效元素” 的目标决定的。

为什么常用 != 条件?

原地修改的核心需求通常是:保留 “符合要求的元素”,剔除 “不符合要求的元素”。这里的 “符合要求” 往往就是 “不等于某个值”(比如移除特定值val时,“有效元素” 就是!= val的元素)。

以双指针法为例:

  • 快指针right负责遍历所有元素,一旦遇到!= val的元素(即 “有效元素”),就需要把它 “保留” 到慢指针left的位置。
  • 慢指针left只在快指针找到有效元素时才移动,最终left的位置就是有效元素的个数。

这种逻辑下,!= 条件直接对应了 “需要保留该元素” 的判断,是最直接、高效的写法。

反过来想:如果用 == 会怎样?

如果用 == val 作为条件,逻辑就会变成 “处理需要移除的元素”,但这样容易出现问题:

  • 比如你最初的代码,遇到== val时试图用后一个元素覆盖,但会漏掉连续的val(比如[2,val,val,3],第一个val被覆盖后,第二个val可能没被处理)。
  • 还可能导致数组越界(当val在数组末尾时)。

因此,与其 “盯着要移除的元素”,不如 “盯着要保留的元素”(用!=),逻辑更清晰,也更少出错。

总结

原地修改的核心是 “保留有效元素”,而有效元素的判定往往就是 “不等于某个值”,因此 != 会成为最常用的条件。这种思路不仅适用于移除元素,还适用于很多类似场景(比如筛选偶数、提取特定范围的数等),本质上都是 “留下符合条件的,丢掉不符合的”。

【我感觉更像是一个新的数组在覆盖,这个left看到要留下的元素就覆盖了,原地修改核心应该还是因为没有开辟新的存储空间】

你说得非常准确!原地修改的核心本质确实是“在不额外开辟新数组空间” 的前提下,通过覆盖原数组元素来模拟 “新数组” 的构建,而left指针就相当于这个 “新数组” 的索引。

具体来说:

  • 原数组的内存空间被重复利用,left指针之前的部分([0, left-1])就是 “新数组” 的有效内容,left之后的元素则是待处理或已废弃的部分。
  • right指针遍历到需要保留的元素(!= val)时,就把它 “复制” 到left的位置 —— 这一步看似是 “覆盖” 原数组,实际是在原空间里构建新数组,left每增加 1,就意味着新数组的长度增加 1。

这种方式的优势很明显:

  • 空间复杂度降到O(1),不需要额外申请与原数组同规模的内存(尤其适合处理大规模数据时节省资源)。
  • 虽然原数组中left之后的元素可能还残留旧值,但题目通常只要求 “新数组长度” 和 “前left个元素正确”,后续元素无需关心,因此不影响结果。

可以说,“覆盖” 是手段,“复用原空间” 是核心,leftright双指针的配合则是实现这一目标的高效方式 —— 既满足了原地修改的约束,又达到了筛选元素的目的。

http://www.dtcms.com/a/520891.html

相关文章:

  • GPIO口输出
  • 专教做美食的网站胶州哪里有做网站的
  • 企业网站制作 徐州网站设计原则的历史
  • 隐私保护与数据安全合规(十三)
  • 2025年高真空共晶炉排名
  • 网站做转链接违反版权吗wordpress页面不显示子类
  • 5.3类的构造方法
  • 视频监控系统原理与计量
  • 蓝桥杯高校新生编程赛第一场题解——Java
  • JavaScript 的优势和劣势是什么?
  • 鸿蒙Next的Camera Kit:开启全场景智慧影像开发新纪元
  • 软件开发包含网站开发吗搭建网站成本
  • asp.net 微网站开发教程比较大的建站公司
  • h5游戏免费下载:小猪飞飞
  • 基于单片机的档案库房漏水检测报警labview上位机系统设计
  • 网站开发图标汕头网站建设seo外包
  • DeepSeek-OCR:光学Token:长上下文建模的范式转变
  • Windows 11 24H2内核堆栈保护:系统安全新盾牌
  • 自定义组件(移动端下拉多选)中使用 v-model
  • Android 14 系统启动流程深度解析:AVB流程
  • 阳春做网站0735郴州招聘信息网
  • 营销型网站建设申请域名网站翻页动画效果
  • NGINX架构特点和实现原理
  • 基于单片机的两路PWM信号输出及频率占空比相位差调节系统
  • uniapp三端影视源码苹果cms自动采集电影视频网站源码前端源码带VIP
  • 算法学习笔记-贪心算法总结
  • 山东省建设工程招标中心网站旅游网站前台怎么做
  • 常见神经网络
  • 公司网站ICP怎么备案呢网站维护界面
  • 建一个在线商城网站重庆做学校网站公司