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

同向双指针:从雪花唯一性到环形最远距离

通过最长不重复子段、最短包含所有种类子段、最短包含所有字母子串以及环形最远距离四道经典题,讲解同向双指针(滑动窗口)的优化思路与C++实现。重点在于从暴力枚举中发现指针单调移动的条件,利用哈希表维护窗口状态,以及处理边界初始化、多种答案更新时机的细节。示例中给出了逛画展的三种不同写法,推荐只记录起点和长度来推导终点,避免初始化干扰。

moshang发布于 2026/6/30更新于 2026/8/1916 浏览
同向双指针:从雪花唯一性到环形最远距离

题图

双指针,或者说滑动窗口、尺取法,本质是利用枚举过程中两个指针单调移动的特性,把两层循环砍成线性。关键不是背模板,而是能看出什么时候指针不用回退。

下面通过四道典型的 OJ 题,看看同向双指针在不同场景下怎么用。

唯一的雪花

题图

链接:唯一的雪花

暴力枚举左端点,右端点往后试探,碰到重复就停。左端点右移后,右端点其实不必回退——因为刚刚扫过的区间已经知道没有重复。于是我们就可以用两个指针维护一个窗口,哈希表记录每个数的出现次数。

  • 右指针 r 每进窗口就把新数的次数加一;
  • 如果某个数的次数超过 1,窗口不合法,这时候左指针 l 向右移动,同时把离开窗口的数的次数减一,直到重新合法;
  • 每次窗口合法时,r - l + 1 就是当前不重复子段的长度,取最大值。
#include<iostream>
#include<unordered_map>
using namespace std;

const int N = 1e6 + 10;
int a[N];

int main(){
    int t; cin >> t;
    while(t--){
        int n; cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i];
        
        int l = 1, r = 1;
        unordered_map<int,int> mp;
        int ret = 1;
        
        while(r <= n){
            mp[a[r]]++; // 进窗口
            while(mp[a[r]] > 1){ // 当右边界元素重复,窗口不合法
                mp[a[l++]]--; // 出窗口
            }
            ret = max(ret, r - l + 1); // 更新结果
            r++;
        }
        cout << ret << endl;
    }
    return 0;
}

逛画展

题图

链接:逛画展

要求找一段最短的连续展览,看遍所有 m 位画家的作品。和'字符串'那题套路一样,都是'最短包含所有种类'。当窗口内的不同画家数达到 m 时,试着收缩左侧,同时更新更短的答案。

这里有个容易踩的坑:初始化区间长度。如果直接用 n 作为最短记录 ret,但 end 却初始化为 n,在某些情况下会输出错误。下面给了三种写法,推荐第三种——只记录起点 begin 和长度 ret,结束时用 begin + ret - 1 推出终点,避免了多个变量初始化打架的问题。

方案一:同时维护 begin 和 end

#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, m;
int kind; // 当前窗口不同画师量
int mp[N];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    int l = 1, r = 1;
    int ret = n;
    int begin = 1;
    int end = n;
    
    while(r <= n){
        if(mp[a[r]]++ == 0) kind++; // 进窗口,种类增加
        while(kind == m){ // 已经看全
            if(ret > r - l + 1){ // 更新更短的
                ret = r - l + 1;
                begin = l;
                end = r;
            }
            if(mp[a[l++]]-- == 1) kind--; // 出窗口,种类可能减少
        }
        r++;
    }
    cout << begin << " " << end << endl;
    return 0;
}

方案二:用大数初始化 ret

#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, m;
int kind;
int mp[N];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    int l = 1, r = 1;
    int ret = 1e7;
    int begin = 1;
    int end = 1;
    
    while(r <= n){
        if(mp[a[r]]++ == 0) kind++;
        while(kind == m){
            if(ret > r - l + 1){
                ret = r - l + 1;
                begin = l;
                end = r;
            }
            if(mp[a[l++]]-- == 1) kind--;
        }
        r++;
    }
    cout << begin << " " << end << endl;
    return 0;
}

方案三:只记起点,终点靠算(推荐)

#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, m;
int kind;
int mp[N];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    
    int l = 1, r = 1;
    int ret = n;
    int begin = 1;
    
    while(r <= n){
        if(mp[a[r]]++ == 0) kind++;
        while(kind == m){
            if(ret > r - l + 1){
                ret = r - l + 1;
                begin = l;
            }
            if(mp[a[l++]]-- == 1) kind--;
        }
        r++;
    }
    cout << begin << " " << begin + ret - 1 << endl;
    return 0;
}

字符串

题图

链接:字符串

这题的本质和逛画展一样,只是种类固定为 26 个小写字母,窗口统计改为数组即可。

#include<iostream>
using namespace std;
string s;
int mp[350]; // 统计每个小写字符出现的次数
int kind; // 窗口的元素种类

int main(){
    cin >> s;
    int l = 0, r = 0;
    int ret = 1e7;
    
    while(r < s.size()){
        if(mp[s[r]]++ == 0) kind++;
        while(kind == 26){
            ret = min(ret, r - l + 1);
            if(mp[s[l++]]-- == 1) kind--;
        }
        r++;
    }
    cout << ret << endl;
    return 0;
}

丢手绢

题图

链接:丢手绢

原理图

环形道路,小朋友随机丢手绢,问最远可能距离。可以转化成:在环上选两点,它们之间较短的那条弧(顺时针或逆时针)的最大值。我们用双指针维护一段顺时针弧长 k,让 r 不断前进累加弧长。一旦 2*k > sum,说明现在顺时针弧已经超过逆时针弧,那么更优的情况可能出现在逆时针弧 sum - k 上。此时收缩左端,出窗口前用 sum - k 更新结果,出窗口后用 k 更新结果。

第一次写这种题容易被两个方向更新绕晕,其实只要记住:k 是顺时针弧,当 2*k > sum 时逆时针弧更短,所以 sum - k 可能更大;而窗口滑动过程中,每次 r 前进一步,k 本身也可能是答案。

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];

int main(){
    int n; cin >> n;
    int sum = 0;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        sum += a[i];
    }
    
    int l = 1, r = 1;
    int ret = 0;
    int k = 0;
    
    while(r <= n){
        k += a[r];
        while(2 * k > sum){ // 顺时针弧超过半圈,用逆时针弧更新
            ret = max(ret, sum - k);
            k -= a[l++];
        }
        ret = max(ret, k); // 用顺时针弧更新
        r++;
    }
    cout << ret << endl;
    return 0;
}

结语图

目录

  1. 唯一的雪花
  2. 逛画展
  3. 方案一:同时维护 begin 和 end
  4. 方案二:用大数初始化 ret
  5. 方案三:只记起点,终点靠算(推荐)
  6. 字符串
  7. 丢手绢
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 基于ROS与Ego-Planner的无人机动态避障仿真实战
  • 算法练习题解:哈希表、前缀和与贪心算法实战
  • Edge 边栏 Copilot 图标消失的修复方案
  • Clang Power Tools:在 Visual Studio 里做 C++ 静态分析
  • 多模态 Agent 图像识别技能开发:JavaScript+Python 全栈实战
  • Windows 系统下载、安装并运行 MinIO 服务及访问 WebUI
  • Python 学习方向建议与核心资源概览
  • 论文解读:基于安全上下文检索的野外越狱攻击可扩展防御
  • Milvus 索引实战:类型选型与 Python 代码示例
  • 使用 llamafile 一键运行本地大模型指南
  • GitHub Pages 零代码搭建免费网站实战指南
  • Ubuntu 20.04 手动安装 JDK 17 实战指南
  • AI 驱动游戏:鸿蒙生态的机会在哪里?
  • Git 原理与使用深入剖析(上)
  • 7 个实用的 Python 自动化脚本示例
  • Java 面试核心知识点总结:Spring、MySQL、并发编程等
  • 基于纯 CSS 实现简洁名片卡片设计
  • C++ 入门指南:编程基础与环境搭建
  • 前端 WebSocket 实时通信实战:从原理到生产级封装
  • DeepSeek 各版本说明与优缺点分析

相关免费在线工具

  • 加密/解密文本

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