双指针题目:复写零

文章目录

题目

标题和出处

标题:复写零

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

相关推荐
睡不醒的kun17 小时前
不定长滑动窗口-求子数组个数
数据结构·c++·算法·leetcode·职场和发展·双指针·滑动窗口
拾光Ծ2 天前
【优选算法】双指针算法:专题二
c++·算法·双指针·双指针算法·c++算法·笔试面试
艾莉丝努力练剑5 天前
【优选算法必刷100题】第007~008题(双指针算法):三数之和、四数之和问题求解
linux·算法·双指针·优选算法
老鼠只爱大米7 天前
LeetCode经典算法面试题 #24:两两交换链表中的节点(迭代法、递归法等多种实现方案详细解析)
算法·leetcode·链表·递归·双指针·迭代·链表交换
沉默-_-7 天前
力扣hot100双指针专题解析2(C++)
java·c++·算法·蓝桥杯·双指针
老鼠只爱大米8 天前
LeetCode经典算法面试题 #19:删除链表的倒数第N个结点(双指针、栈辅助法等多种实现方案详细解析)
算法·leetcode·链表·双指针·删除链表节点·一趟扫描
2401_841495648 天前
【LeetCode刷题】删除链表的倒数第N个结点
数据结构·python·算法·leetcode·链表·遍历·双指针
老鼠只爱大米10 天前
LeetCode经典算法面试题 #160:相交链表(双指针法、长度差法等多种方法详细解析)
算法·leetcode·链表·双指针·相交链表·长度差法
老鼠只爱大米10 天前
LeetCode经典算法面试题 #234:回文链表(双指针法、栈辅助法等多种方法详细解析)
算法·leetcode·链表·递归·双指针·快慢指针·回文链表