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

素。

相关推荐
像污秽一样1 小时前
算法设计与分析-算法效率分析基础-习题1.1
c语言·数据结构·c++·算法
abant22 小时前
leetcode 739 单调栈模板题
算法·leetcode·职场和发展
宝贝儿好7 小时前
【强化学习实战】第十一章:Gymnasium库的介绍和使用(1)、出租车游戏代码详解(Sarsa & Q learning)
人工智能·python·深度学习·算法·游戏·机器学习
weixin_4588726110 小时前
东华复试OJ二刷复盘2
算法
Charlie_lll10 小时前
力扣解题-637. 二叉树的层平均值
算法·leetcode
爱淋雨的男人11 小时前
自动驾驶感知相关算法
人工智能·算法·自动驾驶
wen__xvn11 小时前
模拟题刷题3
java·数据结构·算法
滴滴答滴答答11 小时前
机考刷题之 6 LeetCode 169 多数元素
算法·leetcode·职场和发展
Neteen12 小时前
【数据结构-思维导图】第二章:线性表
数据结构·c++·算法