双指针题目:复写零

文章目录

题目

标题和出处

标题:复写零

出处: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。

  1. 正向遍历数组,直到快指针超出下标范围,此时 fast = 6 \textit{fast} = 6 fast=6, slow = 4 \textit{slow} = 4 slow=4。

  2. 由于 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]。

  3. 将 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]。

  4. 将 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]。

  5. 由于 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)。

相关推荐
锅包一切2 天前
PART2 双指针
c++·算法·leetcode·力扣·双指针
脏脏a4 天前
【优选算法・双指针】以 O (n) 复杂度重构数组操作:从暴力遍历到线性高效的范式跃迁
算法·leetcode·双指针·牛客·优选算法
识君啊5 天前
Java双指针 - 附LeetCode 经典题解
java·算法·leetcode·java基础·双指针
伟大的车尔尼5 天前
双指针题目:下一个排列
双指针
小冻梨6666 天前
ABC444 C - Atcoder Riko题解
c++·算法·双指针
伟大的车尔尼8 天前
双指针题目:压缩字符串
双指针
hnjzsyjyj12 天前
洛谷 P13270:【模板】最小表示法 ← 双指针 + 解环成链
字符串·双指针·解环成链
燃于AC之乐19 天前
《算法实战笔记》第10期:六大算法实战——枚举、贪心、并查集、Kruskal、双指针、区间DP
算法·贪心算法·图论·双指针·区间dp·二进制枚举
睡不醒的kun21 天前
不定长滑动窗口-求子数组个数
数据结构·c++·算法·leetcode·职场和发展·双指针·滑动窗口