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

双指针算法实战:唯一的雪花、逛画展、字符串与丢手绢

双指针算法通过优化暴力枚举策略,利用两个指针不回退的特性降低时间复杂度。讲解滑动窗口在子区间统计中的应用,涵盖四个经典例题:寻找最长无重复字符子串(唯一的雪花)、最短包含所有类型元素的区间(逛画展)、特定字符种类统计(字符串)以及环形距离最值问题(丢手绢)。通过哈希表维护窗口状态,实现 O(n) 或 O(nlogn) 的时间复杂度优化,适合解决涉及连续子序列的统计与查找问题。

DotNetGuy发布于 2026/2/7更新于 2026/6/335 浏览
双指针算法实战:唯一的雪花、逛画展、字符串与丢手绢

在这里插入图片描述

一、双指针

双指针算法有时候也叫尺取法或者滑动窗口,是一种优化暴力枚举策略的手段: • 当我们发现在两层 for 循环的暴力枚举过程中,两个指针是可以不回退的,此时我们就可以利用两个指针不回退的性质来优化时间复杂度。 • 因为双指针算法中,两个指针是朝着同一个方向移动的,因此也叫做同向双指针。 注意:希望大家在学习该算法的时候,不要只是去记忆模板,一定要学会如何从暴力解法优化成双指针算法。不然往后遇到类似题目,你可能压根都想不到用双指针去解决。

二、双指针经典算法题

2.1 唯一的雪花

2.1.1 题目

链接:唯一的雪花

在这里插入图片描述

2.1.2 算法原理

我们「暴力枚举」的过程中,固定一个起点位置 left,然后之后 right 向后遍历时。当 right 第一次扫描到一个位置,使 [left,right] 这个区间「出现重复字符」,此时我们会发现: • right 无需再向后遍历,因为继续向后走区间窗口也是「不合法」的; • left 向后移动一格之后,right 指针也不用回退,因为我们已经维护出来 [left,right] 区间的信息,并且以 left + 1 为起点的最优解一定不会比 left 为起点的好 当我们发现暴力枚举的「两个指针不回退」时,就可以用「滑动窗口」优化: (1)进窗口:right 位置元素记录到统计次数的哈希表中; (2)判断:当哈希表中 right 位置的值出现超过 1 次之后,窗口内子串不合法; (3)出窗口:让 left 所指位置的元素在哈希表中的次数减一; (4)更新结果:判断结束之后,窗口合法,此时更新窗口的大小。 技巧:(1)哈希表可以使用 STL 或者使用数组模拟一个哈希效果(2)依照题意可能在判断条件内也可能在判断条件外

2.1.3 代码
#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;
        ( i = ; i <= n; i++) cin >> a[i];
         l = , r = ;
        unordered_map<,> mp; 
         ret = ;
        (r <= n){
            mp[a[r]]++; 
            (mp[a[r]] > ) 
                mp[a[l++]]--;
            ret = (ret, r - l + ); 
            r++;
        }
        cout << ret << endl;
    }
     ;
}
for
int
1
int
1
1
int
int
// 维护窗口内所有元素出现的次数
int
1
while
// 进窗口
while
1
// 判断---窗口不合法的情况
max
1
// 更新结果
return
0

2.2 逛画展

2.2.1 题目

链接:逛画展

在这里插入图片描述

2.2.2 算法原理

(1)进窗口:right 位置元素记录到统计次数的哈希表中,如果次数是 0 变 1,说明窗口内多了「一种」字符,记录一下; (2)判断:当窗口内字符种类等于 m 时,窗口合法,right 停止右移,接下来需要出窗口; (3)出窗口:让 left 所指位置的元素在哈希表中的次数减一,如果次数是 1 变 0,说明窗口内少了「一种」字符,记录一下; (4)更新结果:在判断成立时,窗口合法,此时更新窗口的大小。

综上:变引出此题答案的多种处理方式也是笔者在做此题遇到的一些坑点分享给大家 (1) 因为此题要输出区间的左右下标故第一种想法便是用两个变量 x 和 y 去记录但初始化会遇到怎么样去赋初值这是会影响结果的:如果使用一个 ret 来记入结果: 第一种情况:ret = n 那么 x 等于 1,y = n;否则在极端条件下当区间确实是包含全部时最后的结果会出错 第二种情况:ret = 一个很大的数(此题要取最优的最短区间)故 x 和 y 赋任何值都可以 (2)第三种情况:仅使用一个变量 x 记录区间的头,使用 x + ret - 1 来直接算出第二个值(最推荐这种方式可以避免很多边界情况)

2.2.3 代码
2.2.3.1 情况一
#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;
}
2.2.3.2 情况二
#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;
}
2.2.3.3 情况三
#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;
}

2.3 字符串

2.3.1 题目

链接:字符串

在这里插入图片描述

2.3.2 算法原理

定义一个变量 kind 记录当前窗口内统计到字符种类 (1)进窗口:right 位置元素记录到统计次数的哈希表中,如果次数是 0 变 1,说明窗口内多了「一种」字符,记录一下; (2)判断:当窗口内字符种类等于 m 时,窗口合法,right 停止右移(寻找到当前情况的最优解),接下来需要出窗口; (3)出窗口:让 left 所指位置的元素在哈希表中的次数减一,如果次数是 1 变 0,说明窗口内少了「一种」字符,记录一下; (4)更新结果:在判断成立时,窗口合法,此时更新窗口大小

2.3.3 代码
#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;
}

2.4 丢手绢

2.4.1 题目

链接:丢手绢

在这里插入图片描述

2.4.2 算法原理

在这里插入图片描述

当我们「暴力枚举」的过程中,固定一个起点位置 left,然后 right 之后向后遍历时,记 [left,right] 之间的距离为 k。当 right 第一次扫描到 k × 2 > sum 时(以 left 为起点到的最远距离为 right - 1),此时我们会发现: • right 无需再向后遍历,因为继续向后走的结果一定不是最优的; • left 向后移动一格之后,right 指针也不用回退,因为我们已经维护出来一个合法区间的信息,right 回退也不是最优解。 解法: (1)进窗口:right 位置与前一个位置的距离累加到 k 中; (2)判断:k × 2 > sum 时,此时 right 指针不用前进,应该让 left 所指的元素出窗口 (3)出窗口:让 left 所指位置与前一个位置的距离累减到 k 中; (4)更新结果:需要在两个地方更新: a. 判断结束之后,此时 [left, right] 之间可能是最优解,用 k 更新结果; b. 判断成立的时候,此时 [right, left] 之间可能是最优解,用 sum − k 更新结果。

2.4.3 代码
#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) // 用 sum - k 来更新结果
        {
            ret = max(ret, sum - k);
            k -= a[l++];
        }
        ret = max(ret, k); // 用 k 来更新结果
        r++;
    }
    cout << ret << endl;
    return 0;
}

总结

双指针算法通过优化暴力枚举策略,利用两个指针不回退的特性降低时间复杂度。本文讲解了滑动窗口在子区间统计中的应用,涵盖四个经典例题:寻找最长无重复字符子串(唯一的雪花)、最短包含所有类型元素的区间(逛画展)、特定字符种类统计(字符串)以及环形距离最值问题(丢手绢)。通过哈希表维护窗口状态,实现 O(n) 或 O(nlogn) 的时间复杂度优化,适合解决涉及连续子序列的统计与查找问题。

目录

  1. 一、双指针
  2. 二、双指针经典算法题
  3. 2.1 唯一的雪花
  4. 2.1.1 题目
  5. 2.1.2 算法原理
  6. 2.1.3 代码
  7. 2.2 逛画展
  8. 2.2.1 题目
  9. 2.2.2 算法原理
  10. 2.2.3 代码
  11. 2.2.3.1 情况一
  12. 2.2.3.2 情况二
  13. 2.2.3.3 情况三
  14. 2.3 字符串
  15. 2.3.1 题目
  16. 2.3.2 算法原理
  17. 2.3.3 代码
  18. 2.4 丢手绢
  19. 2.4.1 题目
  20. 2.4.2 算法原理
  21. 2.4.3 代码
  22. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • ruoyi-vue-pro 数据大屏纯前端单点登录实现
  • 教育领域 NLP 应用与智能问答系统实战
  • 计算机基础知识总结:操作系统、网络、数据库与 C++
  • SHA-256 哈希破解工具技术解析
  • 程序员职业发展的“内卷化”现象与应对策略
  • 算法实战:位运算解决两数之和与缺失数字问题
  • Win11 安装 Node.js 后 npm install 报错:禁止运行脚本
  • 基于 Python 爬虫与 Pi0 具身智能的机器人动作数据采集
  • 基于宝塔面板的 OpenClaw 生产环境部署与安全优化
  • 2026 年 2 月 AIGC 行业模型发布与前沿资讯汇总
  • VSCode Copilot 接入智谱 GLM-5.1 实战指南
  • C++11 右值引用与移动语义详解:从性能瓶颈到零拷贝优化
  • QLoRA 高效微调技术实战:基于 LLaMA-65B 仅需 48G 显存
  • Telegram 中文搜索机器人@letstgbot 技术解析与开发实践
  • Stable Diffusion v1.5 Archive 跨平台效果一致性保障与复现验证
  • 光伏产品缺陷检测 AI 深度学习算法技术方案
  • 飞书 OpenClaw 机器人配置指南:实现 AI 智能对话与自动化办公
  • C++入门:输入输出流、缺省参数与函数重载
  • LangChain4j 集成多模型 Provider 方案:OpenAI 与本地模型混合部署
  • HarmonyOS 开发核心知识点总结(一)

相关免费在线工具

  • 加密/解密文本

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