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

Leetcode 36

1 题目

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

2 代码实现

void moveZeroes(int* nums, int numsSize) {int i = 0 ; for (int j = 0 ; j < numsSize ; j ++){if (nums[j] != 0 ){nums[i] = nums[j];i++;}}for(;i<numsSize; i++){nums[i] = 0 ;}
}

错误百出

void moveZeroes(int* nums, int numsSize) {int i = 0 ; for (int j = 0 ; j < numsSize ; j ++){if (nums[j] != 0 ){nums[i] = nums[j];i++;}}for(;i<numsSize; i++){nums[i+1] = 0 ;i++;}
}

这段代码的意图应该是实现 “将数组中的 0 移动到末尾,同时保持非 0 元素的相对顺序”,但存在明显的逻辑错误,导致无法正确运行。我们来一步步分析:

代码逻辑拆解(意图)

从结构上看,代码试图用两步完成任务:

  1. 第一步(第一个 for 循环):用双指针ij,把所有非 0 元素 “搬运” 到数组前端(类似removeElement的思路)。j遍历所有元素,i记录非 0 元素的位置,当nums[j]≠0时,将其放到nums[i]并让i自增。这一步结束后,i应该指向 “非 0 元素的末尾”,剩下的位置(inumsSize-1)需要填充 0。

  2. 第二步(第二个 for 循环):理论上应该把i及之后的位置全部设为 0,但实际代码写错了。

错误分析

第二个 for 循环的逻辑完全错误:

for(;i<numsSize; i++){nums[i+1] = 0 ;  // 越界风险 + 赋值位置错误i++;  // 重复自增,跳过元素
}
  • 越界问题:当i = numsSize-1时,i+1 = numsSize,超出数组下标范围(数组最大下标是numsSize-1),会导致内存访问错误。
  • 重复自增:for 循环本身已有i++,循环体内又执行i++,导致i每次增加 2,会跳过部分位置,无法将所有剩余位置设为 0。
  • 赋值位置错误:应该给nums[i]赋值 0(当前位置),而不是nums[i+1](下一个位置)。

修正方案

修正第二个循环,正确填充剩余位置为 0:

void moveZeroes(int* nums, int numsSize) {int i = 0;// 第一步:将所有非0元素移到前端for (int j = 0; j < numsSize; j++) {if (nums[j] != 0) {nums[i] = nums[j];i++;}}// 第二步:将剩余位置填充为0(从i到numsSize-1)for (; i < numsSize; i++) {nums[i] = 0;  // 正确赋值当前位置,无越界}
}

修正说明

  • 第一步结束后,i的值等于 “非 0 元素的个数”,此时[0, i-1]已存放所有非 0 元素(保持原顺序)。
  • 第二步循环从i开始,到numsSize-1结束,将这些位置逐个设为 0,确保数组末尾的所有元素都是 0。
  • 时间复杂度O(n),空间复杂度O(1),符合原地修改的要求。

例如,对于输入nums = [0,1,0,3,12],修正后代码会输出[1,3,12,0,0],符合预期。

3 题目

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

提示:

  • 1 <= s.length <= 105
  • s[i] 都是 ASCII 码表中的可打印字符

4 代码实现

void reverseString(char* s, int sSize) {for (int head = 0;head < sSize / 2 ; head++){int end  =  sSize - head -1 ;char temp = 0 ;temp = s[end] ;s[end] = s[head] ;s[head] = temp ;}} 
void reverseString(char* s, int sSize) {int left = 0 ;int right = sSize -1 ;while (left < right){char temp = 0 ;temp = s[left];s[left] = s[right];s[right] = temp ;left ++;right --;}}

第一次写的错误,又是一出惨案......

void reverseString(char* s, int sSize) {for (int head = 0;head < sSize / 2 ; head++){for (int end = sSize - 1 ; end > sSize / 2 ;end -- ){char temp = 0 ;temp = s[end] ;s[end] = s[head] ;s[head] = temp ;}} 
}

你提供的这段代码无法正确反转字符串,原因在于使用了双层循环,导致字符交换逻辑出现错误。具体问题分析如下:

核心问题:错误的循环逻辑

反转字符串的正确思路是对称交换:第 i 个字符与第 sSize-1-i 个字符交换(例如第 1 个和最后 1 个交换,第 2 个和倒数第 2 个交换),只需一层循环即可完成。

但你的代码用了两层循环:

  • 外层循环 head 从 0 遍历到 sSize/2 - 1(这部分没问题,对应前半部分字符)。
  • 内层循环 end 从 sSize-1 遍历到 sSize/2 + 1(这部分错误,会导致重复交换)。

内层循环会让 head 位置的字符与所有 end > sSize/2 的字符依次交换,而不是只交换一次对应的对称位置。

举例说明(以 s = "abcd"sSize=4 为例)

  • 正确目标:反转后应为 "dcba"
  • 你的代码执行过程:
    1. 外层循环 head=0 时,内层循环 end 从 3 遍历到 2(因为 sSize/2=2end > 2 即 end=3):
      • 第一次交换:s[0] 与 s[3] 交换 → s 变为 "dbca"
    2. 外层循环 head=1 时,内层循环 end 从 3 遍历到 2end=3):
      • 第二次交换:s[1] 与 s[3] 交换 → s 变为 "dacb"。最终结果为 "dacb",显然错误。

正确代码(单层循环)

void reverseString(char* s, int sSize) {for (int head = 0; head < sSize / 2; head++) {int end = sSize - 1 - head; // 计算对称位置char temp = s[head];s[head] = s[end];s[end] = temp;}
}

逻辑说明

  • 每次循环中,head 对应前半部分字符的索引,end 直接计算为其对称位置的索引(sSize-1-head)。
  • 仅交换这两个位置的字符一次,循环 sSize/2 次即可完成反转。

更优雅的双指针相向而行

你提到的这个思路(双指针相向而行)确实是反转字符串的经典且高效的实现方式,用 C 语言实现会非常简洁且高效,逻辑也更直观。

C 语言版本实现(对应思路):

void reverseString(char* s, int sSize) {int left = 0;int right = sSize - 1;while (left < right) {// 交换左右指针指向的字符char temp = s[left];s[left] = s[right];s[right] = temp;// 指针向中间移动left++;right--;}
}

为什么这个思路更巧妙?

  1. 时间复杂度最优:只需要遍历字符串的一半(left 和 right 相遇即停止),时间复杂度为 O(n),且没有多余的循环或计算。

  2. 空间复杂度最优:仅使用了 leftrighttemp 三个额外变量,空间复杂度为 O(1),属于原地反转(in-place),不需要额外开辟空间。

  3. 逻辑直观:双指针从两端向中间逼近,每次交换一对对称字符,过程清晰易懂,不容易出错(避免了嵌套循环的逻辑混乱)。

  4. 适用性广:不仅适用于字符串,还可以推广到数组等线性结构的反转,是一种通用的双指针技巧。

这个实现确实比之前的嵌套循环版本更优雅,也是实际开发中反转字符串的首选方式。

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

相关文章:

  • 深入理解epoll:为什么推荐使用epoll_create1而不是epoll_create?
  • 公司被其它人拿来做网站营销渠道的概念
  • 在 Linux 下使用 I2C(Inter-Integrated Circuit)进行用户态编程 — 教程与实战
  • 替代HT1621B液晶驱动显示模块芯片程序演示
  • C++和OpenGL实现3D游戏编程【连载26】——添加TransformComponent组件(设置子物体的位移、旋转、缩放)
  • 常规条形光源在工业视觉检测上的应用
  • Zotero插件安装
  • Llama Factory、Unsloth与Hugging Face三大微调平台深度对比分析
  • 电脑卡在 “正在准备 Windows”?5 步解决:从等待到重装
  • 优惠券网站要怎么做的佛山禅城网站建设
  • 基于深度学习计算s21参数,在射频中的应用
  • 微服务day01——拆分作业参考
  • YOLO11训练后的模型无法正常推理解决办法
  • 网站模版 优帮云深圳网络安全公司排名
  • navicat过期了,怎么操作mysql。用DBeaver
  • LangGraph工作流与智能体核心模式总结
  • redis常见问题解决
  • 网站顶部有空白网络营销是什么时候出现的
  • NFS文件存储
  • 07_朴素贝叶斯
  • 【源码项目】简单实现的WPF浏览器,有兴趣的可以自己扩展(带源码)
  • 大连做网站哪家好一点商务网站建设用的是什么软件
  • Mybatis一级缓存
  • Java+OpenCV实现图片切割
  • Linux云计算基础篇(24)-PXE批量安装和Kickstart工具
  • 从零搭建 RAG 智能问答系统 6:Text2SQL 与工作流实现数据库查询
  • 创建Django项目
  • 注册个人网站的方法建设企业网站费用
  • 初识C语言13.自定义类型(联合体与枚举)
  • WebRTC入门指南:实时通信零基础