文章目录
题目
标题和出处
标题:复写零
出处:1089. 复写零
难度
4 级
题目描述
要求
给定一个长度固定的整数数组 arr \texttt{arr} arr,将该数组中出现的每个零都复写,并将其余的元素向右平移。
超过原数组长度的位置不写入元素。原地修改输入数组,没有返回值。
示例
示例 1:
输入: arr = [1,0,2,3,0,4,5,0] \texttt{arr = [1,0,2,3,0,4,5,0]} arr = [1,0,2,3,0,4,5,0]
输出: [1,0,0,2,3,0,0,4] \texttt{[1,0,0,2,3,0,0,4]} [1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为 [1,0,0,2,3,0,0,4] \texttt{[1,0,0,2,3,0,0,4]} [1,0,0,2,3,0,0,4]。
示例 2:
输入: arr = [1,2,3] \texttt{arr = [1,2,3]} arr = [1,2,3]
输出: [1,2,3] \texttt{[1,2,3]} [1,2,3]
解释:调用函数后,输入的数组将被修改为 [1,2,3] \texttt{[1,2,3]} [1,2,3]。
数据范围
- 1 ≤ arr.length ≤ 10 4 \texttt{1} \le \texttt{arr.length} \le \texttt{10}^\texttt{4} 1≤arr.length≤104
- 0 ≤ arr[i] ≤ 9 \texttt{0} \le \texttt{arr[i]} \le \texttt{9} 0≤arr[i]≤9
解法
思路和算法
用 n n n 表示数组 arr \textit{arr} arr 的长度。如果没有原地修改的限制,则可以创建新数组存储复写零之后的全部元素。由于原数组中的每个零都复写,其余元素都不复写,因此新数组的长度至少为 n n n 且不超过 2 n 2n 2n,将新数组的下标范围 [ 0 , n − 1 ] [0, n - 1] [0,n−1] 的元素复制回原数组,即可得到复写零之后的答案。
上述复写零的过程中,需要对原数组和新数组分别维护遍历到的下标,两个数组的下标以不同的速度移动,原数组的下标每次移动一位,新数组的下标每次移动一位或两位。因此复写零的过程可以通过双指针实现,原数组的下标对应慢指针,新数组的下标对应快指针。
在原地修改的要求下,不能创建新数组,但是仍可以使用双指针。在不创建新数组的情况下,双指针都用于遍历原数组,且只需要考虑下标范围 [ 0 , n − 1 ] [0, n - 1] [0,n−1]。
如果在正向遍历数组的过程中复写零,则复写的零会覆盖尚未遍历的元素,因此需要在反向遍历数组的过程中复写零。为了实现反向遍历,需要定位到复写零之后的 arr [ n − 1 ] \textit{arr}[n - 1] arr[n−1] 在复写零之前位于数组中的下标,然后反向遍历数组并复写零。
定义快指针 fast \textit{fast} fast 和慢指针 slow \textit{slow} slow,慢指针用于遍历数组,快指针用于将复写零之后的结果填入结果数组,初始时两个指针都指向下标 0 0 0。
使用慢指针正向遍历数组,对于每个元素,如果该元素是零则将快指针向右移动两位,如果该元素不是零则将快指针向右移动一位。当快指针超出数组下标范围时,结束正向遍历。
当正向遍历结束之后,下标范围 [ 0 , slow − 1 ] [0, \textit{slow} - 1] [0,slow−1] 中的元素都被遍历,这些元素应填入下标范围 [ 0 , fast − 1 ] [0, \textit{fast} - 1] [0,fast−1] 中,此时 fast = n \textit{fast} = n fast=n 或 fast = n + 1 \textit{fast} = n + 1 fast=n+1。
如果 fast = n + 1 \textit{fast} = n + 1 fast=n+1,则正向遍历数组的过程中, slow \textit{slow} slow 最后遍历到的元素是零,对应 fast \textit{fast} fast 的最后一次移动是向右移动两位,因此复写零之后有 arr [ n − 1 ] = arr [ n ] = 0 \textit{arr}[n - 1] = \textit{arr}[n] = 0 arr[n−1]=arr[n]=0。由于下标 n n n 超出数组下标范围,因此只更新一个元素的值,将 arr [ n − 1 ] \textit{arr}[n - 1] arr[n−1] 更新为 0 0 0。为了避免出现下标越界的问题,对于 fast > n \textit{fast} > n fast>n 的情况需要单独处理(此时一定有 fast = n + 1 \textit{fast} = n + 1 fast=n+1),具体做法是将 slow \textit{slow} slow 向左移动一位,将 fast \textit{fast} fast 向左移动两位,然后将 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast] 的值更新为 0 0 0。
当 fast ≤ n \textit{fast} \le n fast≤n 时,每次将 slow \textit{slow} slow 向左移动一位之后,判断 arr [ slow ] \textit{arr}[\textit{slow}] arr[slow] 是否为零,并按如下方式填写元素。
-
如果 arr [ slow ] ≠ 0 \textit{arr}[\textit{slow}] \ne 0 arr[slow]=0,则将 fast \textit{fast} fast 向左移动一位,然后将 arr [ slow ] \textit{arr}[\textit{slow}] arr[slow] 填入 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast]。
-
如果 arr [ slow ] = 0 \textit{arr}[\textit{slow}] = 0 arr[slow]=0,则将 fast \textit{fast} fast 向左移动两位,然后将 arr [ slow ] \textit{arr}[\textit{slow}] arr[slow] 填入 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast] 和 arr [ fast + 1 ] \textit{arr}[\textit{fast} + 1] arr[fast+1]。
重复上述操作,直到所有元素都填写结束。
由于 fast \textit{fast} fast 与 slow \textit{slow} slow 之差等于 slow \textit{slow} slow 遍历过的元素中的零的个数,因此当 fast = slow \textit{fast} = \textit{slow} fast=slow 时,下标范围 [ 0 , slow − 1 ] [0, \textit{slow} - 1] [0,slow−1] 中的所有元素在复写零前后相同,此时即可结束上述操作。
下面用一个例子说明双指针的做法。
给定数组 arr = [ 1 , 0 , 2 , 0 , 3 ] \textit{arr} = [1, 0, 2, 0, 3] arr=[1,0,2,0,3],长度为 n = 5 n = 5 n=5。
-
正向遍历数组,直到快指针超出下标范围,此时 fast = 6 \textit{fast} = 6 fast=6, slow = 4 \textit{slow} = 4 slow=4。
-
由于 fast > n \textit{fast} > n fast>n,因此将 slow \textit{slow} slow 向左移动一位,将 fast \textit{fast} fast 向左移动两位,然后将 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast] 的值更新为 0 0 0,此时 fast = 4 \textit{fast} = 4 fast=4, slow = 3 \textit{slow} = 3 slow=3, arr = [ 1 , 0 , 2 , 0 , 0 ] \textit{arr} = [1, 0, 2, 0, 0] arr=[1,0,2,0,0]。
-
将 slow \textit{slow} slow 向左移动一位, slow = 2 \textit{slow} = 2 slow=2,由于 arr [ slow ] ≠ 0 \textit{arr}[\textit{slow}] \ne 0 arr[slow]=0,因此将 fast \textit{fast} fast 向左移动一位,然后将 arr [ slow ] \textit{arr}[\textit{slow}] arr[slow] 填入 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast],此时 fast = 3 \textit{fast} = 3 fast=3, arr = [ 1 , 0 , 2 , 2 , 0 ] \textit{arr} = [1, 0, 2, 2, 0] arr=[1,0,2,2,0]。
-
将 slow \textit{slow} slow 向左移动一位, slow = 1 \textit{slow} = 1 slow=1,由于 arr [ slow ] = 0 \textit{arr}[\textit{slow}] = 0 arr[slow]=0,因此将 fast \textit{fast} fast 向左移动两位,然后将 arr [ slow ] \textit{arr}[\textit{slow}] arr[slow] 填入 arr [ fast ] \textit{arr}[\textit{fast}] arr[fast] 和 arr [ fast + 1 ] \textit{arr}[\textit{fast} + 1] arr[fast+1],此时 fast = 1 \textit{fast} = 1 fast=1, arr = [ 1 , 0 , 0 , 2 , 0 ] \textit{arr} = [1, 0, 0, 2, 0] arr=[1,0,0,2,0]。
-
由于 fast = slow \textit{fast} = \textit{slow} fast=slow,因此复写零的过程结束,结果数组是 arr = [ 1 , 0 , 0 , 2 , 0 ] \textit{arr} = [1, 0, 0, 2, 0] arr=[1,0,0,2,0]。
代码
java
class Solution {
public void duplicateZeros(int[] arr) {
int n = arr.length;
int fast = 0, slow = 0;
while (fast < n) {
if (arr[slow] == 0) {
fast += 2;
} else {
fast++;
}
slow++;
}
if (fast > n) {
slow--;
fast -= 2;
arr[fast] = 0;
}
while (fast > slow) {
fast--;
slow--;
int curr = arr[slow];
arr[fast] = curr;
if (curr == 0) {
fast--;
arr[fast] = curr;
}
}
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 arr \textit{arr} arr 的长度。双指针正向和反向遍历数组各一次。
-
空间复杂度: O ( 1 ) O(1) O(1)。