文章目录
- 前言
- 一、复写零
- 1, 题目
- 2, 思路分析
- 2.1, 从左往右 or 从右往左
- 2.2, 找到最后一个保留的数
- 3, 代码展示
前言
各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你:
📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
📗 Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
📘 JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)
一、复写零
1, 题目
OJ链接
注意, 本题要求原地操作, 不能开辟额外数组空间 ! !
「数组分两块」是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部分。这种类型的题,⼀般就是使⽤「双指针」来解决
2, 思路分析
2.1, 从左往右 or 从右往左
上篇文章 介绍了 “移动零”, 底层思路和本题类似, 都是利用双指针来划分数组, "移动零"中双指针是从左往右遍历的, 这也是一般的尝试解法, 但本题不能从左往右遍历
先来尝试从左往右遍历 :
- 定义第一个指针 cur(当前) , 用来遍历数组, 判定当前数据为零还是非零
- 定义第二个指针 dest(目的地), 开始时和 cur 同步
- 如果 cur 指向零, 则继续遍历
- 如果 cur 指向非零, 则 dest 指向的值修改成 0 , dest++(执行两次)
过程如图 :
双指针具体是从左往右还是从右往左遍历, 根据实际情况判断, 本题中只能使用从右往左了
如果题目不要求原地复写, 我们可以开辟一个同样大小的数组, 在新数组上执行上述操作, 就不会有覆盖数据的现象了
首先从结果导向分析一下 :
原数组 : [1,0,2,3,0,4
,5,0] ----> 复写后输出:[1,0,0,2,3,0,0,4
], 说明 : 4
之后的数据都被删除了, 所以复写过程中, 这个 4
是最后一个被保留的数
- 定义第一个指针 cur(当前) , 开始时指向
4(原数组的 5 下标)
, 用来遍历数组, 判定当前数据为零还是非零 - 定义第二个指针 dest(目的地), 开始时指向数组最后一个数据
- 如果 cur 指向零, 则 dest 指向的值修改成 0 , dest-- (执行两次)
- 如果 cur 指向非零, 则 dest 指向的值修改成 cur 指向的值
dest 的意思是目的地, 在从右往左遍历时, 目的地是 0 下标, 这正是 while 循环条件
过程如图 :
结果符合预期
2.2, 找到最后一个保留的数
上述例子中的 cur 的初始位置是 数组中最后一个保留的数, 我们用肉眼找到了 4
, 那如何用代码找到 cur 的初始位置呢 ? 也是利用双指针, 从左往右遍历
-
cur 和 dest 初始化为 -1 下标, 从左往右遍历
-
如果 cur 指向零, 则 dest++ 两次
-
如果 cur 指向非零, 则 dest++ 一次
-
当 dest 走到数组末尾时, cur 就是最后一个保留的数
所以正确的步骤是 : 1, 先找最后一个保留的数 2, 复写操作
但需注意 ! ! !
当原数为 : [1,5,2,0,6,8,0,6,0], 最后一个保留的数是零, 由于 cur 指向零, 需要 dest++ 两次, 那么循环结束后, dest 不是在数组末尾, 而是越界了一个单位 ! ! !
需要对 dest 单独判断, 如果 dest 越界, 不能再 [dest] = 0, 而是直接 [dest - 1] - 0
3, 代码展示
public void duplicateZeros(int[] arr) {
// 1, 先找到最后一个需要复写的数
int dest = -1;
int cur = -1;
while(dest < arr.length - 1) {
cur++;
if(arr[cur] != 0) {
dest++;
}
if(arr[cur] == 0) {
dest += 2;
}
}
// 2, 从右往左复写
while(dest >= 0 ) {
if(arr[cur] != 0) {
arr[dest] = arr[cur];
dest--;
}else {
// 判断 dest 是否越界
if(dest == arr.length) {
arr[dest - 1] = 0;
}else {
arr[dest] = 0;
arr[dest - 1] = 0;
}
dest -= 2;
}
cur--;
}
}