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

进阶:你能尽量减少完成的操作次数吗?

我的想法:

不能复制数组,又用哈希吧?或者用一个判断?我不知道啊,快慢指针?根本不知道从哪里写比较合适。

1 分析题目

这道题要求我们原地(in-place)将数组中的所有 0 移动到末尾,同时保持非零元素的相对顺序。关键点有:

  1. 原地操作 - 不能创建新数组,必须直接修改原数组
  2. 保持非零元素顺序 - 非零元素的相对位置不能改变
  3. 最小化操作次数 - 这是进阶要求

你的思路分析

你的思考方向是正确的!提到了哈希表、判断条件和快慢指针,这些都是解决数组问题的常用思路:

  • 哈希表:在这个问题中不需要,因为我们不需要记录元素频率或快速查找
  • 判断条件:是的,我们需要判断元素是否为 0
  • 快慢指针:这是解决这个问题的最佳方法!

2 正确解法:快慢指针法

快慢指针法是解决这类 "原地移动元素" 问题的经典方法。具体步骤:

  1. 快指针遍历数组,寻找非零元素
  2. 慢指针记录当前非零元素应该存放的位置
  3. 当快指针找到非零元素时,将其值赋给慢指针位置,然后慢指针前进一位
  4. 最后,将慢指针之后的所有位置填充为 0

java

运行

class Solution {public void moveZeroes(int[] nums) {int slow = 0;// 第一次遍历,将所有非零元素移到前面for (int fast = 0; fast < nums.length; fast++) {if (nums[fast] != 0) {nums[slow] = nums[fast];slow++;}}// 第二次遍历,将slow之后的所有位置填充为0for (int i = slow; i < nums.length; i++) {nums[i] = 0;}}
}

优化版本:减少操作次数

上面的方法需要两次遍历数组,实际上我们可以通过一次遍历来完成:

优化版本(一次遍历)

java

运行

class Solution {public void moveZeroes(int[] nums) {int slow = 0;for (int fast = 0; fast < nums.length; fast++) {if (nums[fast] != 0) {// 交换非零元素到前面int temp = nums[slow];nums[slow] = nums[fast];nums[fast] = temp;slow++;}}}
}

3 复杂度分析

  • 时间复杂度:O (n),只需要遍历一次数组
  • 空间复杂度:O (1),只需要常数级额外空间

你的不足与改进建议

  1. 对快慢指针不够熟悉:这是解决原地数组问题的重要技巧,建议多练习类似题目(如移除元素、删除有序数组中的重复项等)
  2. 缺乏解题模板:对于数组问题,可以总结常见的解题思路:双指针(快慢指针、左右指针)、滑动窗口、排序等
  3. 动手实践不足:不要害怕开始写代码,先尝试实现一个基本解法,再考虑优化

4 练习建议

推荐练习以下 LeetCode 题目来巩固快慢指针技巧:

  1. 26. 删除有序数组中的重复项
  2. 27. 移除元素
  3. 80. 删除有序数组中的重复项 II

5 快慢指针和左右指针

官方给出的题解:

class Solution {public void moveZeroes(int[] nums) {int n = nums. length , left = 0 , right = 0 ;while (right < n ){if (nums[right] != 0 ){swap (nums, left ,right);left++;}right++;}}public void swap (int[]nums , int left ,int right){int temp =nums[left];nums[left] = nums [right];nums[right] = temp ;}
}

在这个 Java 实现中,left 和 right 指针本质上是快慢指针,而非传统意义上的左右指针。这两种指针模式的核心区别如下:

1. 快慢指针(Slow-Fast Pointers)

  • 同向移动:两个指针从同一侧(通常是左侧)开始,以不同速度遍历数组。
  • 核心逻辑
    • 慢指针:负责维护符合条件的元素位置(例如,非零元素的插入位置)。
    • 快指针:负责快速遍历数组,寻找目标元素(例如,非零元素)。
  • 典型应用
    • 移除数组中的特定元素(如移除 0、重复项)。
    • 判断链表是否有环(Floyd 判圈算法)。

你的代码属于快慢指针

  • left 指针:只有当 right 找到非零元素时才移动,相当于 “慢指针”。
  • right 指针:每次循环都移动,相当于 “快指针”。
  • 当 right 遇到非零元素时,通过交换将其移到 left 位置,然后 left 前进。

2. 左右指针(Left-Right Pointers)

  • 相向移动:两个指针分别从数组的左右两端开始,向中间移动。
  • 核心逻辑
    • 左指针:从左向右移动,寻找需要交换到右侧的元素。
    • 右指针:从右向左移动,寻找需要交换到左侧的元素。
  • 典型应用
    • 反转数组(如反转字符串)。
    • 两数之和(有序数组)。
    • 快速排序中的分区操作。

左右指针的示例代码(反转数组):

java

运行

public void reverse(int[] nums) {int left = 0, right = nums.length - 1;while (left < right) {int temp = nums[left];nums[left] = nums[right];nums[right] = temp;left++;right--;}
}

3. 两者对比总结

特征快慢指针左右指针
移动方向同向(左→右)相向(左←→右)
速度关系快指针移动更快指针交替移动或按需移动
核心目的筛选 / 移动特定元素反转、查找配对元素
典型场景移除元素、链表操作反转数组、两数之和

4. 你的代码分析

你的实现使用了快慢指针,因为:

  1. left 和 right 都从左侧开始,同向移动。
  2. right 指针快速遍历数组,left 指针仅在交换时前进。
  3. 通过交换非零元素到左侧,保持了非零元素的相对顺序。

如果使用左右指针(相向移动),则无法保证非零元素的顺序(例如,可能将右侧的非零元素交换到左侧,打乱原有顺序)。因此,本题必须使用快慢指针

总结

  • 快慢指针:同向移动,用于筛选 / 移动元素,保持顺序
  • 左右指针:相向移动,用于反转、配对或分区操作
  • 你的代码是快慢指针的正确应用,适合本题需求

6 先做一下复制到新数组的方法

题友的启发:

“没有这么复杂,大家先做一下复制到新数组的方法(遍历nums,所有非0数复制到新数组),然后再想一想,怎么优化到使用原数组做为新数组即可。”

明白了!我们可以先通过复制到新数组的方法直观地解决问题,再逐步优化到原地操作。以下是完整的思路和代码实现:

方法一:复制到新数组(直观但不符合题目要求)

步骤

  1. 遍历原数组 nums,将所有非零元素按顺序放入新数组。
  2. 新数组剩余位置补 0。
  3. 将新数组复制回原数组(题目要求原地修改)。

代码

java

运行

public void moveZeroes(int[] nums) {int n = nums.length;int[] temp = new int[n];int index = 0;// 复制非零元素到新数组for (int num : nums) {if (num != 0) {temp[index++] = num;}}// 剩余位置补0while (index < n) {temp[index++] = 0;}// 将新数组复制回原数组System.arraycopy(temp, 0, nums, 0, n);
}

复杂度

  • 时间:O (n)
  • 空间:O (n)(不符合题目要求)

方法二:原地操作(快慢指针优化)

优化思路
直接在原数组上操作,通过双指针将非零元素移到左侧,剩余位置补 0。

步骤

  1. 慢指针 left:记录下一个非零元素应存放的位置。
  2. 快指针 right:遍历数组,找到非零元素时与 left 交换,并右移 left
  3. 遍历结束后,left 之后的位置全部补 0。

代码

java

运行

public void moveZeroes(int[] nums) {int left = 0;// 将非零元素移到左侧for (int right = 0; right < nums.length; right++) {if (nums[right] != 0) {nums[left] = nums[right];left++;}}// 剩余位置补0while (left < nums.length) {nums[left++] = 0;}
}

复杂度

  • 时间:O (n)
  • 空间:O (1)(符合题目要求)

方法三:进一步优化(一次遍历 + 交换)

思路
在方法二的基础上,通过交换操作避免第二次遍历补 0。

步骤

  1. 双指针 left 和 right 从左侧开始。
  2. 当 right 遇到非零元素时,与 left 交换,并同时右移两指针。
  3. 交换后,left 左侧的元素均为非零,left 指向的位置为下一个可能需要交换的 0。

代码

java

运行

public void moveZeroes(int[] nums) {int left = 0;for (int right = 0; right < nums.length; right++) {if (nums[right] != 0) {// 交换非零元素到左侧int temp = nums[left];nums[left] = nums[right];nums[right] = temp;left++;}}
}

复杂度

  • 时间:O (n)(仅一次遍历)
  • 空间:O (1)

对比与总结

方法时间复杂度空间复杂度操作次数
复制到新数组O(n)O(n)2n
原地操作(两次遍历)O(n)O(1)2n
原地操作(一次遍历)O(n)O(1)n

结论
方法三最优,通过一次遍历和交换操作,直接将所有非零元素移到左侧,同时将 0 移到右侧,操作次数最少。

7 System.arraycopy是什么?

System.arraycopy 是 Java 中用于数组复制的一个高效方法,位于 java.lang.System 类中。它允许你将一个数组的部分或全部元素复制到另一个数组中,是 Java 标准库中性能最优的数组复制方式之一。

核心语法

java

运行

public static void arraycopy(Object src,      // 源数组int srcPos,      // 源数组的起始位置Object dest,     // 目标数组int destPos,     // 目标数组的起始位置int length       // 需要复制的元素个数
)

关键特性

  1. 高效性:基于 JVM 底层实现,通常比手动循环复制快得多。
  2. 灵活性:支持部分复制(指定起始位置和长度)。
  3. 兼容性
    • 仅适用于相同类型的数组(基本类型或引用类型)。
    • 对于引用类型,复制的是对象引用而非对象本身(浅拷贝)。
  4. 异常处理
    • 如果参数不合法(如数组类型不匹配、越界等),会抛出 ArrayStoreException 或 IndexOutOfBoundsException

示例代码

1. 基本类型数组复制

java

运行

int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];// 将 src 中索引 1 开始的 3 个元素复制到 dest 的索引 2 位置
System.arraycopy(src, 1, dest, 2, 3);// 输出 dest: [0, 0, 2, 3, 4]
2. 对象数组复制(浅拷贝)

java

运行

class Person {public int age;public Person(int age) { this.age = age; }
}Person[] src = {new Person(20), new Person(30)};
Person[] dest = new Person[2];System.arraycopy(src, 0, dest, 0, 2);// 修改 dest[0] 的属性会影响 src[0]
dest[0].age = 25;
System.out.println(src[0].age); // 输出: 25

与其他复制方式的对比

方法适用场景性能特点
System.arraycopy同类型数组高效复制最优底层实现,支持部分复制
Arrays.copyOf创建新数组并复制全部元素中等封装了 arraycopy
Arrays.copyOfRange创建新数组并复制部分元素中等封装了 arraycopy
for 循环自定义复杂复制逻辑最差灵活但效率低
clone()对象数组的浅拷贝中等仅适用于实现 Cloneable 的类

注意事项

  1. 避免数组重叠:如果源数组和目标数组是同一数组,且复制区域有重叠,可能导致意外结果。例如:

    java

    运行

    int[] arr = {1, 2, 3, 4, 5};
    // 错误示例:重叠复制可能导致数据覆盖
    System.arraycopy(arr, 0, arr, 2, 3); // 结果可能不符合预期
    

  2. 多维数组:对于多维数组,arraycopy 只复制顶层数组的引用,不会递归复制子数组(仍为浅拷贝)。

常见应用场景

  • 数组扩容:创建更大的新数组并复制原内容。
  • 批量数据处理:高效转移数组中的部分数据。
  • 集合实现:Java 中的 ArrayList 等集合类内部使用 arraycopy 实现动态扩容。

掌握 System.arraycopy 可以帮助你在 Java 中更高效地处理数组操作,尤其是在性能敏感的场景中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93617.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93617.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93617.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 56 - 合并区间

思路 排序&#xff1a;将所有区间按起始点从小到大排序。贪心合并&#xff1a;初始化一个结果列表&#xff0c;放入第一个区间。然后遍历剩余区间&#xff0c;将当前区间与结果列表中的最后一个区间比较&#xff1a; 若重叠&#xff08;当前区间起点 ≤ 结果区间终点&#xff0…

DNS 正向查找与反向查找

DNS 区域是 DNS 中基本的组织单元&#xff0c;为域名定义了管理和权威边界。一个 DNS 区域通常包含一系列 DNS 资源记录&#xff0c;包括名称到地址的映射&#xff08;正向查找&#xff09;和地址到名称的映射&#xff08;反向查找&#xff09;。这些区域对于高效管理和解析网络…

Oracle ERP FORM开发 — 新增查询条件

1 根据值来查询具体流程步骤看第2节&#xff0c;这里提供核心的增加查询条件的触发器代码&#xff1a;1.1 可完全匹配的值比如“是”&#xff0c;“否”&#xff0c;“物料”&#xff0c;“”汽车 等等这些可以直接通过对应的值匹配&#xff0c;特点就是词语&#xff0c;短小。…

Flutter实现列表功能

在Flutter中,可以通过ListView和ListTile等组件来实现类似Android中RecyclerView和Adapter的功能。以下是一个通用的设计架构,用于设计列表数据: 1. 定义数据模型 首先,定义一个数据模型类,用于存储列表中每一项的数据。例如: class ItemModel {final String title;fi…

2.1、Redis的单线程本质和多线程的操作

Redis的单线程本质 1. 核心单线程部分 #mermaid-svg-iFErSltthPIEsuiP {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-iFErSltthPIEsuiP .error-icon{fill:#552222;}#mermaid-svg-iFErSltthPIEsuiP .error-text{fil…

文件权限值的表示方法

文章目录字符表示方法8 进制数值表示方法字符表示方法 Linux表示说明Linux表示说明r--只读-w-仅可写--x仅可执行rw-可读可写-wx可写可执行r-x可读可执行rwx可读可写可执行---无权限 8 进制数值表示方法 权限符号8进制2进制r4100w2010x1001rw6110rx5101wx3011rwx7111---0000

【38】WinForm入门到精通 ——WinForm平台为AnyCPU 无法切换为x64,也无法添加 x64及其他平台

WinForm 是 Windows Form 的简称&#xff0c;是基于 .NET Framework 平台的客户端&#xff08;PC软件&#xff09;开发技术&#xff0c;是 C# 语言中的一个重要应用。.NET 提供了大量 Windows 风格的控件和事件&#xff0c;可以直接拿来使用。本专栏内容是按照标题序号逐渐深入…

门控激活函数:GLU/GTU/Swish/HSwish/Mish/SwiGLU

10 门控激活函数 10.1 GLU&#xff1a;门控线性单元函数Gated Linear Unit10.2 GTU&#xff1a;门控Tanh单元函数Gated Tanh Unit自门控激活函数&#xff08;Self-gated activation function&#xff09;是一种通过自身机制动态调节信息流动的激活函数&#xff0c;其核心在于模…

Linux内核IPv4多播路由深度解析:从数据结构到高效转发

多播路由是网络通信的核心技术之一&#xff0c;Linux内核通过精密的多层设计实现了高性能的IPv4多播路由功能。本文将深入剖析其核心实现机制&#xff0c;揭示其高效运转的秘密。一、核心数据结构&#xff1a;路由系统的基石1. 多播路由表&#xff08;struct mr_table&#xff…

ffmpeg-7.1.1 下载安装 windows 版,MP4 转 m3u8 切片,遇到报错 Unrecognized option ‘vbsf‘的解决办法

工作中偶尔会需要造指定大小的文档文件&#xff0c;不要求内容&#xff0c;可以随意填充任意无毒内容&#xff0c;所以打算用ts文件填充&#xff0c;现记录下过程。一、下载 ffmpeg废话不多说&#xff0c;上链接&#xff0c;https://ffmpeg.org/会跳转新页面&#xff0c;向下拉…

Linux网络编程:网络基础概念(下)

目录 前言&#xff1a; 一、网络传输基本流程 1.1、认识MAC地址 1.2、认识IP地址 二、socket编程预备 2.1、端口号 2.2、传输层的代表 2.3、网络字节序 2.4、sockaddr 结构 总结&#xff1a; 前言&#xff1a; 大家好&#xff0c;上一篇文章&#xff0c;我们说到了…

亚马逊广告进阶指南:如何优化流量实现新品快速起量

“新品上架如何快速获取精准流量&#xff1f;”“如何降低ACOS同时提升转化率&#xff1f;”“竞品流量拦截有哪些高效方法&#xff1f;”“关键词广告和ASIN广告如何协同投放&#xff1f;”“人工投放效果不稳定&#xff0c;AI工具真的能解决问题吗&#xff1f;”如果你也在思…

路径平滑优化算法--Clothoid 路径平滑

路径平滑优化算法–Clothoid 路径平滑 文章目录路径平滑优化算法--Clothoid 路径平滑0 为什么选 Clothoid&#xff1f;1 数学基础&#xff1a;严谨推导&#xff08;Mathematical Foundation&#xff09;可视化解释1.1 曲率线性假设1.2 切向角&#xff08;Heading Angle&#…

PCB学习笔记(一)

文章目录一、PCB的制作过程了解1.1 覆铜板一、核心作用&#xff1a;制作印制电路板&#xff08;PCB&#xff09;二、不同类型覆铜板的针对性用途三、延伸应用1.2 覆铜板和信号线的关系一、覆铜板不是“全是铜”&#xff0c;原始结构是“绝缘基材铜箔”二、信号线就是铜&#xf…

【19】C# 窗体应用WinForm ——【列表框ListBox、复选列表框CheckedListBox】属性、方法、实例应用

文章目录9 复选列表框CheckedListBox10. 列表框ListBox10.1 实例&#xff1a;买菜10.2 实例&#xff1a;购菜 应用二WinForm 是 Windows Form 的简称&#xff0c;是基于 .NET Framework 平台的客户端&#xff08;PC软件&#xff09;开发技术&#xff0c;是 C# 语言中的一个重要…

新注册企业信息查询“数据大集网”:驱动企业增长的源头活水

商贸繁荣的齐鲁大地上&#xff0c;“赶大集”曾是千年传承的民间智慧。而今天&#xff0c;一场以新注册企业信息为核心的“数据大集”正悄然重塑商业生态——数据大集网&#xff0c;以“聚天下好数&#xff0c;促万企互联”为使命&#xff0c;将分散的企业信息转化为精准商机&a…

项目目标如何拆解,才能提高执行效率和效果

项目目标要有效拆解以提高执行效率和效果&#xff0c;需要遵循以下关键步骤&#xff1a;明确整体目标、划分阶段性目标和里程碑、具体化任务细分并设定优先级、分配明确的责任人和时间节点、持续跟踪与反馈调整。其中&#xff0c;划分阶段性目标和里程碑尤为重要&#xff0c;通…

spring-ai-alibaba动态 Prompt 最佳实践

Spring AI Alibaba 使用 Nacos 的配置中心能力来动态管理 AI 应用的 Prompt。以此来实现动态更新 Prompt 的功能。 环境准备 Nacos: 具备配置中心能力的 Nacos&#xff0c;本例中使用 Nacos 3.0.2。Nacos 2.X 亦可&#xff0c; spring-ai版本1.0.0 &#xff0c;spring-ai-al…

基于词频统计、关键词提取、情感分析与AI大模型自动生成系统的设计与实现

文章目录有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主一、研究背景与项目意义二、项目目标与研究内容三、系统架构与功能模块1. AI对话生成模块2. 分词与关键词提取模块3. 情感分析模块4. 行为预测模块5. 系统管理模块6. 可视化展示模块四、技术…

JVM工具

首先&#xff0c;JDK 自带了很多监控工具&#xff0c;都位于 JDK 的 bin 目录下&#xff0c;其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。 一、jps(Java Process Status) 用于查看有权访问的虚拟机的进程&#xff0c;并显示他们的进程号 -v&#xff1a;列出虚拟…