算法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--;
            }
        }
    }
}

素。

相关推荐
橘白3165 分钟前
rl笔记(一):策略梯度更新算法推导
人工智能·算法·机器人·强化学习
hhhhhaaa6 分钟前
多节点矩阵式任务系统:统一配置中心与动态规则引擎架构设计
后端·算法·架构
吃着火锅x唱着歌16 分钟前
LeetCode 739.每日温度
算法·leetcode·职场和发展
如竟没有火炬18 分钟前
去除重复字母——贪心+单调栈
开发语言·数据结构·python·算法·leetcode·深度优先
薛定e的猫咪28 分钟前
【ICML 2025】MODULI:基于扩散模型解锁离线多目标强化学习的偏好泛化
人工智能·学习·算法·机器学习
Brilliantwxx31 分钟前
【C++】priority_queue以及 仿函数 的学习
开发语言·c++·笔记·学习·算法
风味蘑菇干31 分钟前
斗地主案例
java·数据结构·算法
Languorous.1 小时前
C++数据结构高阶|B+树深度解析:从底层原理到数据库应用,面试高频考点全覆盖
数据结构·b树·面试
洛水水1 小时前
【力扣100题】39.二叉树的最近公共祖先
算法·leetcode·职场和发展
无敌昊哥战神1 小时前
【LeetCode 134】加油站:图解指针跳跃与 O(N) 极简贪心,避开 Python 隐藏坑!
c语言·python·算法·leetcode