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

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

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

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

前言

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

双指针的作用

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

  • 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. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 模型预测控制(MPC)算法原理及仿真搭建
  • YOLOv3 C++ DLL 调用与 CUDA 依赖配置
  • 线性动态规划基础详解:4 道经典例题与优化技巧
  • 钉钉 Webhook 机器人配置与 @ 用户实现指南
  • Copilot 最佳使用方式与深度配置指南
  • Whisper-WebUI 本地部署与核心功能详解
  • 高可用集群架构对比与迁移落地指南
  • Open WebUI MCPo 项目解析:将 MCP 工具转换为 OpenAPI 接口
  • Spring Boot 入门:Spring Web MVC 核心概念与实战解析
  • OpenClaw 接入微信教程:一条命令把 AI 助手接进微信 【2026年3月22日】最新官方版
  • MATLAB 与 Python 混合编程技术原理与实战
  • ExcelJS 使用教程:JavaScript Excel 处理库
  • Claude Code 本地安装与环境变量配置指南
  • 多模态大模型 Llama 3.2 正式发布,支持视觉推理与边缘部署
  • 二分查找算法原理与实战应用
  • 深入理解梯度提升决策树 (GBDT) 原理与实现
  • A*算法在网格路径规划中的三种优化策略对比与实战
  • uv 与 conda 对比:Python 环境管理工具选型指南
  • C++ 有限状态自动机(FSM):原理、实现与应用全解析
  • 深入解析 Stable Diffusion 基石:潜在扩散模型(LDMs)

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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