【优选算法:双指针】——复写零题解

目录

一、算法概述

二、例题讲解

[2.1 题目1:复写零](#2.1 题目1:复写零)

[2.1.1 思路讲解:](#2.1.1 思路讲解:)

[2.1.2 做题步骤:](#2.1.2 做题步骤:)

1) 找到最后一个被复写的元素的位置 找到最后一个被复写的元素的位置)

2) 从右向前复写 从右向前复写)

[2.1.3 代码实现(Java/c++)](#2.1.3 代码实现(Java/c++))


一、算法概述

指针算法实质上就是用两个变量标识列表中的具体位置,来达到目标。常见的双指针一般分为两类:左右指针 和 快慢指针。

  • 左右指针 :两个指针分别从数组 / 序列的左右两端向中间移动,也叫对撞指针。多用于有序数组,实现二分查找、两数之和、反转数组等场景,一左一右根据条件不断收缩区间。
  • 快慢指针:两个指针从同一侧起点出发,一个走得快、一个走得慢(步长不一样)。典型用于链表,判断链表是否有环、找链表中点;也可以用于数组,原地移除元素。

对于算法的讲解,文字叙述我不想写太多,主要还是通过例题讲解让大家真正领悟双指针算法。单纯记概念很容易遗忘,看懂例题、看懂指针每一步怎么移动,才能吃透双指针的思想:不产生额外数组,仅通过下标移动,降低时间、空间复杂度,很多时候可以把暴力 O (n²) 优化到 O (n)。

二、例题讲解

2.1 题目1:复写零

力扣题目地址:1089. 复写零 - 力扣(LeetCode):

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地进行上述修改,不要从函数返回任何东西。

2.1.1 思路讲解 :

首先题目中明确是 就地修改 ,就明白不能够新开辟空间进行复写。此时想到使用两个指针模拟复写一下示例1,一般都会想从左向右复写,定义两个指针,cur表示遍历当前要复写的元素位置,dest表示待复写位置,初始cur=0,dest=-1。cur位置非零时dest走1步,但cur为零dest则走2步,如图:

结果分析:

由上图所示,由于dest指针的速度是不稳定的,所以dest指针的位置可能会跑到cur指针的前面,那么从左向右复写就可能导致每次cur走到的位置都为0,导致后续的元素全被复写成0了。

那么换一个角度思考,从左到右复写不行,那从右到左呢?由于复写后的数组长度要和复写前的数组长度保持一致 ,那么只要数组中存在0元素,那么一定会有末尾的元素被丢掉 。所以想要从右向左复写元素,有一个前提,就是找到最后一个被复写的元素的位置!

2.1.2 做题步骤:

1) 找到最后一个被复写的元素的位置

从示例1的答案不难看出,对于数组 1,0,2,3,0,4,5,0,最后一个被复写的元素是4,那么如何直接找到这个这个位置呢?此时可以考虑前面从左向右复写的方式,但此时,"不复写,纯遛弯儿"。

步骤如下:

  1. 当cur < n (n表示数组的长度) 时,判断cur位置的元素是否为0;
  2. 若cur位置的元素为0,dest走2步,反之dest走1步;
  3. 判断dest是否越界,若没越界,则cur走一步,反之结束循环;

对应

当dest 触达数组最后下标 n-1 的时候停止,此时cur的位置的元素就是最后一个被复写的元素。此时,再开始从后往前复写。

但别着急,此时可能有边界情况,比如当数组为 1,0,2,3,0,4,当找到最后一个被复写的元素时,dest的位置刚好越界,如果这种情况下直接开始复写,就会导致发生下标越界的异常。所以在复写前就要做出边界处理。

2) 从右向前复写

步骤如下:

  1. 判断dest位置是否越界,如果越界,直接将n-1位置上的元素修改为0,dest-=2,cur-=1;
  2. 若不越界,直接开始复写;
  3. 判断cur位置元素的值,为0时 dest-=2;为非0时 dest-=1;
  4. 最后cur--,复写下一个位置的元素,注意cur>=0;

以例题1为例:

2.1.3 代码实现(Java/c++)

  • java代码实现:
java 复制代码
class Solution {
    public void duplicateZeros(int[] arr) {
        int cur=0;
        int dest=-1;
        int n=arr.length;
        //1.找到最后一个被复写的元素位置
        while(cur<n){
            if(arr[cur]==0) dest+=2;
            else dest++;
            if(dest>=n-1) break;
            cur++;
        }

         //2.处理边界情况
            if(dest==n){
                arr[n-1]=0;
                dest-=2;
                cur--;
            }

        //3.开始复写
        while(cur>=0){
            if(arr[cur]==0){
                arr[dest--]=0;
                arr[dest--]=0;
                cur--;
            }else{
                arr[dest--]=arr[cur--];
            }
        }
    }
}
  • c++代码实现:
cpp 复制代码
#include <vector>
using namespace std;

class Solution
{
public:
    void duplicateZeros(vector<int>& arr)
    {
        // 1. 先找到最后一个数
        int cur = 0, dest = -1, n = arr.size();
        while(cur < n)
        {
            if(arr[cur]) dest++;
            else dest += 2;
            if(dest >= n - 1) break;
            cur++;
        }
        // 2. 处理一下边界情况
        if(dest == n)
        {
            arr[n - 1] = 0;
            cur--; dest -=2;
        }
        // 3. 从后向前完成复写操作
        while(cur >= 0)
        {
            if(arr[cur]) 
                arr[dest--] = arr[cur--];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
};

这道题是原地双指针非常经典的入门例题。核心思想一句话总结:正向遍历只模拟不写入,定位最后一个有效元素;再反向拷贝,避免未复制的数据被覆盖。

很多同学一开始会直接从左向右原地写,踩掉数据覆盖的坑。记住:原地修改数组,当写指针跑的比读指针更快时,优先考虑反向填充。

本题的边界判断 dest==n 是面试高频考点,建议手动推演[0,1,2]这个案例,吃透这个特殊场景。

下一篇我们继续双指针的其他经典题型。

相关推荐
Dola_Zou37 分钟前
工商业储能系统核心策略
算法·自动化·软件工程
我命由我1234539 分钟前
Python 开发 - Python 项目部署(Windows 与 Linux)
java·linux·windows·后端·python·java-ee·python3.11
小羊没烦恼!44 分钟前
TypedSql:在 C# 类型系统上实现一个 SQL 查询引擎
java·linux·运维·服务器·开发语言·数据库·mysql
inquisiter1 小时前
KernelGpt的部分尝试
java·前端·apache
aibo8603081 小时前
基于 Spring Boot + Vue3 的餐饮食材采购与成本分析系统设计与实现
java·spring boot·后端
Geek-Chow1 小时前
Spring Boot: Integrating Swagger / OpenAPI With springdoc-openapi
java
Rain的Java大神之路1 小时前
面了上百人,我最爱问的LeetCode 1「两数之和」:能背代码的很多,能讲清“先查后存”的没几个
java·后端·算法
薛之谦_1 小时前
Java 整合 RocketMQ 实战:从入门到复杂场景落地
java·rocketmq·java-rocketmq
aibo8603081 小时前
基于 Spring Boot + Vue3 的菜谱分享网站设计与实现
java·spring boot·后端