算法2,复写零

1089. 复写零 - 解题思路整理

题目分析

问题描述

给定一个固定长度的整数数组 arr,对数组中的每个 0 进行复写(每个 0 在数组中占据两个位置),并将其余元素向右平移。如果数组长度不足,则舍弃超出长度的元素

核心要求

  1. 对数组中的 0 进行复写(每个 0 变成两个 0)

  2. 非 0 元素保持原值

  3. 修改必须在原数组中进行

  4. 从数组末尾开始处理,避免覆盖未处理的元素

示例说明

复制代码
示例1:输入: [1,0,2,3,0,4,5,0]
        输出: [1,0,0,2,3,0,0,4]
        
示例2:输入: [0,1,2,3,0,4]
        输出: [0,0,1,2,3,0]
        特殊情况:最后一个0被舍弃,因为数组长度不足

算法分析

1. 问题难点

  • 从前往后复写会覆盖还未处理的元素

  • 需要先知道哪些元素会被保留,哪些会被舍弃

  • 必须从后往前进行复写操作

2. 双指针法(从后向前)

算法思想

  1. 第一阶段:模拟从前往后复写的过程,找到最后一个会被保留的元素

  2. 第二阶段:从后往前进行实际的复写操作

指针定义

  • cur:从前往后扫描指针,用于模拟复写过程

  • dest:从前往后复写时的目标位置指针

  • 在第二阶段,curdest的含义会变化


第一阶段:寻找最后一个复写的数

目标:确定在复写后,数组中最后一个元素是什么,以及它是从哪里复写过来的。

初始化

  • cur = 0:当前处理的元素位置

  • dest = -1:如果在这里复写,应该写入的位置

处理逻辑

复制代码
while (cur < arr.length) {
    if (arr[cur] != 0) {
        // 非0元素,dest只移动一步
        dest++;
    } else {
        // 0元素,dest需要移动两步(复写)
        dest += 2;
    }
    
    // 判断dest是否已到达或超过数组末尾
    if (dest >= arr.length - 1) {
        break;  // 找到最后一个复写的元素
    }
    cur++;
}

特殊情况处理

复制代码
// 如果dest正好等于arr.length(越界1位)
if (dest == arr.length) {
    // 说明最后一个位置需要放置一个0的复写
    arr[arr.length - 1] = 0;
    dest -= 2;  // dest回退两位
    cur--;      // cur回退一位
}

执行过程示例

复制代码
输入: [1,0,2,3,0,4,5,0]  (长度8)

步骤    cur    dest     arr[cur]    操作
0)      0      -1       1           dest=0
1)      1       0       0           dest=2
2)      2       2       2           dest=3
3)      3       3       3           dest=4
4)      4       4       0           dest=6
5)      5       6       4           dest=7
6)      6       7       5           dest=8 (停止)

此时:
- dest = 8 (已越界,需要特殊处理)
- cur = 6 (指向元素5)
- 最后一个被处理的元素是5

第二阶段:从后往前复写

目标:从后向前遍历数组,将元素复写到正确位置。

处理逻辑

复制代码
// 此时cur指向最后一个被处理的元素
// dest指向最后一个应该写入的位置

while (cur >= 0 && dest >= 0) {
    if (arr[cur] != 0) {
        // 非0元素,直接复制
        arr[dest] = arr[cur];
        dest--;
    } else {
        // 0元素,需要复写两次
        if (dest >= 1) {
            arr[dest] = 0;
            arr[dest - 1] = 0;
        } else if (dest == 0) {
            // dest=0时,只能写入一个0
            arr[dest] = 0;
        }
        dest -= 2;
    }
    cur--;
}

示例执行(继续上面的例子):

复制代码
第一阶段结束后:
cur = 6, dest = 8 (越界,需调整)
arr = [1,0,2,3,0,4,5,0]

特殊情况处理:
dest == 8 > 7,需要特殊处理
arr[7] = 0  (最后一个位置放0)
dest = 8-2 = 6
cur = 6-1 = 5

从后往前复写:
cur=5, dest=6: arr[5]=4 → arr[6]=4
cur=4, dest=5: arr[4]=0 → arr[5]=0, arr[4]=0
cur=3, dest=3: arr[3]=3 → arr[3]=3
cur=2, dest=2: arr[2]=2 → arr[2]=2
cur=1, dest=1: arr[1]=0 → arr[1]=0, arr[0]=0
cur=0, dest=-1: 结束

最终结果: [0,0,1,2,3,0,0,4]

完整算法步骤总结

步骤1:寻找最后一个复写的数

  1. 初始化 cur = 0, dest = -1

  2. 遍历数组:

    • 如果 arr[cur] == 0dest += 2

    • 否则,dest += 1

    • dest >= arr.length-1时停止

  3. 处理边界情况:

    • 如果 dest == arr.length,在末尾放一个0,dest-=2, cur-=1

步骤2:从后往前复写

  1. cur开始向前遍历:

    • 如果 arr[cur] != 0arr[dest--] = arr[cur]

    • 如果 arr[cur] == 0

      • arr[dest--] = 0

      • 如果 dest >= 0arr[dest--] = 0

  2. 继续直到 cur < 0


时间复杂度与空间复杂度

时间复杂度:O(n)

  • 第一阶段:遍历一次数组,找到最后一个元素

  • 第二阶段:反向遍历一次数组

  • 总共两次遍历,O(2n) = O(n)

空间复杂度:O(1)

  • 只使用了常数个额外变量

  • 满足原地操作的要求


关键技巧总结

  1. 双向指针:一个指针模拟复写过程,一个指针记录目标位置

  2. 从后往前处理:避免覆盖未处理的元素

  3. 边界处理:特别注意数组末尾的0复写可能导致越界

  4. 分阶段处理:先"模拟"找到边界,再实际执行

这种方法的核心思想是先虚拟地走一遍复写过程,确定哪些元素会被保留,然后从后往前进行实际复写,确保不会覆盖还未处理的元

复制代码
class Solution {
    public void duplicateZeros(int[] arr) {
        //先找到最后一个复写的数字
        int cur=0;
        int dest=-1;
        for(cur=0;cur<arr.length;cur++){
            if(arr[cur]!=0){
                dest++;
            }else{
                dest+=2;
            }
            if(dest>=arr.length-1)
            break;
        }
        //特殊情况
        if(dest==arr.length){
            arr[arr.length-1]=0;
            cur--;
            dest-=2;
        }
        //从后往前完成复写操作
        while(cur>=0){
            if(arr[cur]!=0){
                arr[dest--]=arr[cur--];
            }else{
            arr[dest--]=0;
            arr[dest--]=0;
            cur--;
            }
        }
    }
}

素。

相关推荐
qeen876 分钟前
【算法笔记】差分与经典例题解析
c语言·c++·笔记·学习·算法·差分
HZ·湘怡7 分钟前
双链表 -- 带头 双向 循环 链表
数据结构·链表·双链表
kronos.荒11 分钟前
动态规划——整数拆分(python)
python·算法·动态规划
cici1587418 分钟前
基于Koopman模型预测控制的非线性流控制数据驱动框架
算法
6Hzlia19 分钟前
【Hot 100 刷题计划】 LeetCode 416. 分割等和子集 | C++ 0-1背包 1D空间极致优化
c++·算法·leetcode
穿条秋裤到处跑21 分钟前
每日一道leetcode(2026.04.21):执行交换操作后的最小汉明距离
java·算法·leetcode
Tina学编程22 分钟前
算法训练Day10 | LeetCode 169 多数元素
算法·leetcode
eggrall23 分钟前
Leetcode 将 x 减到 0 的最小操作数
数据结构
sheeta199830 分钟前
LeetCode 每日一题笔记 日期:2026.04.22 题目:2452. 距离字典两次编辑以内的单词
笔记·算法·leetcode
Lazionr36 分钟前
【链表经典OJ-上】
c语言·数据结构·链表