跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

双指针算法实战:移动零与复写零解析

双指针算法实战:移动零与复写零解析。通过移动零和复写零两道经典力扣题目,深入讲解双指针在数组处理中的应用。核心思路是利用两个指针分别标记遍历位置和有效数据位置,通过一次或两次遍历完成元素重排。重点在于理解指针区间划分及边界条件处理,特别是复写零问题中从右向左遍历的必要性,以确保空间复杂度为 O(1)。

板砖工程师发布于 2026/3/26更新于 2026/9/1063 浏览
双指针算法实战:移动零与复写零解析

前言

在算法的世界里,双指针技巧常常能发挥出神奇的作用。今天,我们就来精讲两道利用双指针解决的经典题目:移动零和复写零。由于这两道题目均为数组操作,这里的双指针算法指的是利用数组下标代替指针。当我们遇到数组分块、数组划分的问题时,可以考虑使用双指针法。

双指针的作用

两个指针的主要作用如下:

  • cur:从左往右扫描数组,遍历整个数组。
  • dest:已处理的区间内,非零元素的最后一个位置。

这通常将数组分为三个区间:[0, dest](全是非 0 的元素)、[dest + 1, cur - 1](都是 0)、[cur, n - 1](还未处理过的)。

移动零

题目链接:【力扣】Move Zeroes 题目描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例: 输入:nums = {0,1,0,3,12} 输出:{1,3,12,0,0}

解题思路

我们可以使用双指针法来解决这个问题。一个指针 cur 用于遍历整个数组,另一个指针 dest 用于指向当前非零元素应该放置的位置。当遇到非零元素时,将其放置在 dest 指针所指的位置,并将 dest 指针向后移动一位。遍历结束后,从 dest 指针开始到数组末尾的位置全部设置为零。

两个指针将数组分为三个区间:

  • [0, dest]:全是非 0 的元素(已经处理)
  • [dest + 1, cur - 1]:都是 0(已经处理)
  • [cur, n - 1]:还未处理过的

代码实现(以 C++ 为例)

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        // dest 用于标记已处理的非零元素的最后位置
        int dest = -1;
        // cur 用于遍历整个向量
        int cur = 0;
        while (cur < nums.size()) {
            // 如果当前位置的元素为 0
            if (nums[cur] == 0) {
                cur++;
            } else {
                // 先将 dest 加 1,标记下一个非零元素应放置的位置
                swap(nums[++dest], nums[cur]);
                cur++;
            }
        }
    }
};

复杂度分析

  • 空间复杂度:O(1),只使用了有限的额外空间。
  • 时间复杂度:O(n),其中 n 是数组的长度。我们只需要遍历一次数组。

复写零

题目链接:【力扣】Duplicate Zeros 题目描述:给你一个长度固定的整数数组 arr,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。注意:不要在超过该数组长度的位置写入元素。

示例: 输入:arr = {1,0,2,3,0,4,5,0} 输出:{1,0,0,2,3,0,0,4}

解题思路

同样可以使用双指针法来解决这个问题。一个指针 cur 用于遍历数组,另一个指针 dest 用于指向复写零后数组中元素应该放置的位置。当遇到零元素时,将 dest 指针后的元素依次向后移动两位,并在 dest 和 dest+1 的位置都放置零。当遇到非零元素时,将其放置在 dest 指针所指的位置,并将 dest 指针向后移动一位。

为什么从右往左?

这个解题思路是怎么来的呢?如果我们尝试从左往右遍历,当 arr[cur] != 0 时,让 arr[dest] = arr[cur],然后 cur--, dest--;当 arr[cur] == 0 时,让 arr[dest--] = 0, arr[dest--] = 0, cur--。这样遍历没有问题,因此我们选择从右往左遍历。所以我们只要找到最后要'复写'的数即可。

首先我们从左往右遍历数组,确定 dest 的最终位置。当 arr[cur] != 0 时,我们让 dest 的后面一个的值赋予 arr[cur] 正指向的那个值;当 arr[cur] == 0 时,我们让 dest 的后两个值都赋予 0。当走到这一步时,cur 找不到下一个为 2 的值了,因此我们不能简单地从左往右遍。

我们需要找最后一个要复写的数。起初让 cur 指向数组的开头,dest 指向 -1 的位置。

代码实现(以 C++ 为例)

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        // dest 用于标记复制零元素后的新位置,初始值为 -1
        int dest = -1;
        // cur 用于遍历原始数组,初始值为 0
        int cur = 0;
        int n = arr.size();
        // 遍历原始数组,确定复制零元素后的新位置
        while (cur < n) {
            // 如果当前元素不为 0
            if (arr[cur] != 0) {
                // dest 向后移动一位
                dest++;
            } else {
                // 如果当前元素为 0,dest 向后移动两位(因为要复制一个零)
                dest += 2;
            }
            // 如果 dest 已经到达或超过新数组的最后一个位置,跳出循环
            if (dest >= n - 1) break;
            // cur 向后移动一位,继续遍历原始数组
            cur++;
        }
        // 如果 dest 正好等于新数组的长度
        if (dest == n) {
            // 将新数组的最后一个位置设为 0
            arr[n - 1] = 0;
            // dest 回退两位
            dest -= 2;
            // cur 回退一位,因为上一步 cur 多走了一步
            cur--;
        }
        // 从后往前遍历原始数组,进行复制操作
        while (cur >= 0) {
            // 如果当前元素不为 0
            if (arr[cur] != 0) {
                // 将当前元素复制到新位置
                arr[dest] = arr[cur];
                // cur 和 dest 都向前移动一位
                cur--;
                dest--;
            } else {
                // 如果当前元素为 0,先将 0 复制到 dest 位置,再将另一个 0 复制到 dest - 1 位置
                arr[dest--] = 0;
                arr[dest--] = 0;
                // cur 向前移动一位
                cur--;
            }
        }
    }
};

复杂度分析

  • 空间复杂度:O(1),只使用了有限的额外空间。
  • 时间复杂度:O(n),其中 n 是数组的长度。我们需要遍历两次数组。

总结

通过这两道题目,我们可以看到双指针算法在处理数组相关问题时的高效性和灵活性。希望大家在今后的算法学习中,能够熟练掌握双指针技巧,解决更多复杂的问题。

目录

  1. 前言
  2. 双指针的作用
  3. 移动零
  4. 解题思路
  5. 代码实现(以 C++ 为例)
  6. 复杂度分析
  7. 复写零
  8. 解题思路
  9. 为什么从右往左?
  10. 代码实现(以 C++ 为例)
  11. 复杂度分析
  12. 总结

更多推荐文章

查看全部
  • Flutter 组件 tavily_dart 在鸿蒙系统的适配与进阶应用
  • 基于 Vue 3 和 Hiprint 的 Web 打印设计器 vg-print:拖拽设计与静默打印
  • 基于 Web 的上机管理系统设计与开发
  • Spring Boot Starter 自定义开发实战:构建企业级组件库
  • LLM 微调实战指南:Pythia 模型 Fine Tuning 全流程解析
  • 数据结构初阶:二叉树的链式存储与实现
  • llama.cpp 性能调优指南:提升本地部署效率
  • RabbitMQ 核心工作模式详解与 Java 实战
  • Spring Boot 事件机制详解:原理与示例
  • Prism 工具简介、安装使用及案例应用详解
  • 前端地图开发基础:服务类型、坐标系与 SDK 简介
  • ThinkPHP 和 Laravel 框架的基于 Web 的在线考试答题游戏设计与实现
  • 前端函数防抖详解:原理、手写与 Lodash 实战
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署推理微调指南
  • Llama.cpp 跨平台部署本地大模型实战指南
  • C语言标准库与工具链:string.h、stdio.h、stdlib.h及CMake构建
  • Fooocus 部署实践:本地手动配置与云端一键启用对比
  • 如何成为卓越的 AIGC 产品经理:从传统产品到 AI 领域的转型指南
  • Python 基础语法详解
  • ComfyUI 整合包安装与使用指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online