算法基础篇:(六)基础算法之双指针 —— 从暴力到高效的优化艺术

目录

前言

[一、双指针算法是什么?------ 不止是 "两个指针" 那么简单](#一、双指针算法是什么?—— 不止是 “两个指针” 那么简单)

[1.1 核心定义与本质](#1.1 核心定义与本质)

[1.2 双指针的核心前提](#1.2 双指针的核心前提)

[1.3 双指针的常见类型](#1.3 双指针的常见类型)

[二、为什么要学双指针?------ 暴力解法的 "救命稻草"](#二、为什么要学双指针?—— 暴力解法的 “救命稻草”)

[2.1 暴力枚举的痛点](#2.1 暴力枚举的痛点)

[2.2 双指针的优化](#2.2 双指针的优化)

[三、双指针算法的通用模板 ------ 三步搞定滑动窗口](#三、双指针算法的通用模板 —— 三步搞定滑动窗口)

[3.1 通用模板框架](#3.1 通用模板框架)

[3.2 模板关键要点](#3.2 模板关键要点)

[四、经典例题实战 ------ 从易到难吃透双指针](#四、经典例题实战 —— 从易到难吃透双指针)

[例题 1:唯一的雪花(洛谷 P2563)------ 无重复元素的最长子数组](#例题 1:唯一的雪花(洛谷 P2563)—— 无重复元素的最长子数组)

题目描述

题目分析

暴力解法(超时警告)

双指针优化思路

代码实现

思路总结

[例题 2:逛画展(洛谷 P1638)------ 包含所有元素的最短子数组](#例题 2:逛画展(洛谷 P1638)—— 包含所有元素的最短子数组)

题目描述

题目分析

暴力解法(超时警告)

双指针优化思路

代码实现

思路总结

[例题 3:字符串(牛客网)------ 包含所有小写字母的最短子串](#例题 3:字符串(牛客网)—— 包含所有小写字母的最短子串)

题目描述

题目分析

双指针优化思路

代码实现

思路总结

[例题 4:丢手绢(牛客网)------ 环形数组的最短距离](#例题 4:丢手绢(牛客网)—— 环形数组的最短距离)

题目描述

题目分析

暴力解法(超时警告)

双指针优化思路

代码实现

思路总结

五、双指针算法的常见误区与避坑指南

[5.1 指针回退](#5.1 指针回退)

[5.2 辅助数据结构选择不当](#5.2 辅助数据结构选择不当)

[5.3 边界条件处理不当](#5.3 边界条件处理不当)

[5.4 忘记优化输入输出](#5.4 忘记优化输入输出)

六、双指针算法的拓展应用场景

总结


前言

在算法学习的道路上,我们总会遇到这样的场景:明明用暴力枚举能解决问题,却因为数据量太大导致超时;明明感觉思路没问题,却卡在时间复杂度的瓶颈上。而双指针算法,正是解决这类问题的 "神兵利器"------ 它通过巧妙地维护两个指针,让原本 O (n²) 的暴力解法优化到 O (n),用极简的思路实现高效运算。今天,我们就来全方位拆解双指针算法,从原理到实战,从基础到进阶,带你真正吃透这个基础却不简单的算法思想。下面就让我们正式开始吧!


一、双指针算法是什么?------ 不止是 "两个指针" 那么简单

1.1 核心定义与本质

首先我们应该要明确:双指针并不是特指某一种固定的算法,而是一种优化暴力枚举的思想。它的核心是通过两个指针(可以是数组下标、迭代器等)的协同移动,在一次遍历中完成原本需要多次遍历才能实现的功能,从而降低时间复杂度。

从本质上来说,双指针算法是 "空间换时间" 思想的反向应用 ------ 它不需要额外开辟大量空间,而是通过优化指针移动的逻辑,减少无效遍历,让时间复杂度从暴力枚举的 O (n²)、O (n³) 骤降到 O (n) 或 O (n log n)。

1.2 双指针的核心前提

不是所有问题都能用双指针解决,它的适用场景有一个关键前提:问题具有 "单调性" 或 "二段性",使得两个指针无需回退,只需同向或反向移动

什么是 "无需回退"?举个例子:如果我们用两个指针 leftright遍历数组,当 right向右移动后,left不需要向左退回,而是继续保持在当前位置或向右移动 ------ 这种特性让双指针能够在一次遍历中完成任务。如果指针需要频繁回退,那双指针就失去了优化意义,此时不如直接使用暴力枚举。

1.3 双指针的常见类型

根据指针移动方向和功能,双指针主要分为以下两类:

  • 同向双指针(滑动窗口) :两个指针从同一端出发向相同方向移动,形成一个 "窗口",通过调整窗口的左右边界来解决问题(如子数组、子串相关问题)。
  • 反向双指针 :两个指针从两端出发向中间移动,直到相遇或满足特定条件(如两数之和、数组反转等)。

注:本文重点讲解同向双指针(即滑动窗口),这是算法竞赛和笔试中最常考的类型,后续会结合具体例题深入分析。

二、为什么要学双指针?------ 暴力解法的 "救命稻草"

2.1 暴力枚举的痛点

我们先来看一个经典问题:给定一个长度为 n 的数组,找出其中不包含重复元素的最长子数组长度。

暴力解法的思路是很直接的:枚举所有可能的子数组,判断每个子数组是否包含重复元素,最后记录最长长度。代码如下:

cpp 复制代码
// 暴力枚举:找出无重复元素的最长子数组长度
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

int longestNoRepeatSubarray(vector<int>& nums) {
    int n = nums.size();
    int maxLen = 0;
    // 枚举所有子数组的起点
    for (int i = 0; i < n; i++) {
        unordered_set<int> st;
        int len = 0;
        // 枚举所有子数组的终点
        for (int j = i; j < n; j++) {
            if (st.count(nums[j])) {
                break;
            }
            st.insert(nums[j]);
            len++;
            maxLen = max(maxLen, len);
        }
    }
    return maxLen;
}

int main() {
    vector<int> nums = {1,2,3,2,1};
    cout << longestNoRepeatSubarray(nums) << endl; // 输出3
    return 0;
}

这段代码的时间复杂度是 O (n²) ,当 n=1e5 时,1e10 次运算会直接超时 ------ 这就是暴力解法的致命弱点:面对大规模数据时完全无能为力

2.2 双指针的优化

同样的问题,用双指针优化后,时间复杂度会降到O (n)。我们来看优化的思路:

  • leftright两个指针表示当前子数组的左右边界,初始时都指向数组开头。
  • 用一个哈希表记录窗口内元素的出现次数。
  • right向右移动,将当前元素加入窗口:
    • 如果当前元素在窗口中未重复,继续移动 right,更新最长长度。
    • 如果当前元素重复,移动 left,将窗口左边界向右收缩,直到窗口内不再有重复元素。
  • 重复上述过程,直到 right遍历完数组。

优化后的代码:

cpp 复制代码
// 双指针优化:O(n)时间复杂度
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

int longestNoRepeatSubarray(vector<int>& nums) {
    int n = nums.size();
    int maxLen = 0;
    unordered_set<int> st;
    int left = 0;
    // 仅需一次遍历
    for (int right = 0; right < n; right++) {
        // 窗口内有重复元素,收缩左边界
        while (st.count(nums[right])) {
            st.erase(nums[left]);
            left++;
        }
        st.insert(nums[right]);
        // 更新最长长度
        maxLen = max(maxLen, right - left + 1);
    }
    return maxLen;
}

int main() {
    vector<int> nums = {1,2,3,2,1};
    cout << longestNoRepeatSubarray(nums) << endl; // 输出3
    return 0;
}

为什么时间复杂度是 O (n)呢?因为 left 和 right 都只会向右移动,不会回退,每个元素最多被访问两次(一次被 right 加入窗口,一次被 left 移出窗口),因此总操作次数就是 O (n) 级别。

这就是双指针的魅力:在不增加额外空间复杂度的前提下,让算法效率实现质的飞跃。

三、双指针算法的通用模板 ------ 三步搞定滑动窗口

通过大量实战总结,同向双指针(滑动窗口)问题可以归纳为一个通用模板,核心分为**"进窗口、判条件、出窗口、更结果"**四个步骤。掌握这个模板,大部分滑动窗口问题都能迎刃而解。

3.1 通用模板框架

cpp 复制代码
// 双指针(滑动窗口)通用模板
#include <iostream>
#include <vector>
using namespace std;

int slidingWindowTemplate(vector<int>& nums) {
    int n = nums.size();
    int left = 0; // 窗口左边界
    int result = 0; // 存储最终结果
    // 可以根据需求定义辅助数据结构(哈希表、计数器等)
    // 例如:unordered_map<int, int> cnt; // 统计窗口内元素出现次数
    //      int valid = 0; // 标记窗口是否满足条件

    for (int right = 0; right < n; right++) {
        // 步骤1:进窗口------将当前元素加入窗口,更新辅助数据结构
        // 例如:cnt[nums[right]]++;
        //      if (cnt[nums[right]] == 1) valid++;

        // 步骤2:判条件------判断窗口是否需要收缩(根据题目要求调整)
        // 条件可能是:窗口内有重复元素、窗口内元素和超过阈值、窗口满足目标要求等
        while (/* 窗口不满足条件/需要优化 */) {
            // 步骤3:出窗口------将左边界元素移出窗口,更新辅助数据结构
            // 例如:cnt[nums[left]]--;
            //      if (cnt[nums[left]] == 0) valid--;
            left++; // 收缩左边界
        }

        // 步骤4:更结果------窗口此时满足条件,更新最优结果
        // 例如:result = max(result, right - left + 1);
    }

    return result;
}

3.2 模板关键要点

  1. 进窗口:始终是 right 指针在移动,将当前元素纳入窗口,需要更新辅助数据结构(如计数、求和等)。
  2. 判条件:这是最核心的步骤,需要根据题目具体要求设计判断逻辑。常见的判断条件包括:窗口内有重复元素、窗口内元素和超过目标值、窗口内包含所有需要的元素等。
  3. 出窗口:当窗口不满足条件或需要优化时,移动 left 指针收缩窗口,同时更新辅助数据结构。
  4. 更结果:只有当窗口满足条件时,才更新结果(如最长长度、最短长度、元素和等)。

注意:判断条件的逻辑决定了窗口的性质 ------ 是 "求最长" 还是 "求最短",是 "求存在" 还是 "求最优"。后续例题会详细讲解如何根据题目调整判断条件。

四、经典例题实战 ------ 从易到难吃透双指针

理论终究要落地,下面我们通过 4 道经典例题,从基础到进阶,带你逐步掌握双指针的应用技巧。每道题都会按照 "题目分析→暴力解法→双指针优化→代码实现→思路总结" 的流程讲解,帮助大家理解从暴力到优化的思考过程。

例题 1:唯一的雪花(洛谷 P2563)------ 无重复元素的最长子数组

题目链接: https://www.luogu.com.cn/problem/UVA11572

题目描述

企业家 Emily 想把独特的雪花打包出售,一个包裹里不能有两片相同的雪花。给定通过机器的雪花序列(每个雪花用一个整数标记),求不包含重复雪花的最大包裹大小(即最长无重复元素子数组的长度)。

输入:第一行是测试数据组数 T,每组数据第一行是雪花总数 n(n≤1e6),接下来 n 行每行一个整数表示雪花的标记。

输出:对于每组数据,输出最大包裹的大小。

题目分析

这道题和我们之前讲的**"无重复元素最长子数组"** 是完全一致的,核心需求就是找到最长的不包含重复元素的连续子数组。

暴力解法(超时警告)

枚举所有子数组,判断是否包含重复元素,记录最长长度。时间复杂度O (n²),n=1e6 时超时。

双指针优化思路
  • leftright表示当前窗口的左右边界,初始值为 0。
  • 哈希表 mp记录窗口内雪花的出现次数。
  • right向右移动,将当前雪花加入窗口:
    • 如果 mp [nums [right]] > 1,说明窗口内有重复元素,需要移动 left 收缩窗口,直到mp [nums [right]] == 1
    • 每次移动后,更新最长窗口长度。

代码实现

cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

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

int main() {
    ios::sync_with_stdio(false); // 加速输入输出
    cin.tie(0);
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        unordered_map<int, int> mp;
        int left = 0;
        int max_len = 0;
        for (int right = 0; right < n; right++) {
            // 进窗口:记录当前雪花出现次数
            mp[a[right]]++;
            // 判条件:窗口内有重复元素,收缩左边界
            while (mp[a[right]] > 1) {
                mp[a[left]]--;
                left++;
            }
            // 更结果:更新最长长度
            max_len = max(max_len, right - left + 1);
        }
        cout << max_len << endl;
    }
    return 0;
}

思路总结

这道题是双指针的入门级应用,核心是**"窗口内不允许重复元素"** 。当 right遇到重复元素时,left必须移动到重复元素的下一个位置,确保窗口内始终无重复。由于 leftright 都只向右移动,时间复杂度是 O (n),能够轻松处理 n=1e6 的数据。

例题 2:逛画展(洛谷 P1638)------ 包含所有元素的最短子数组

题目链接: https://www.luogu.com.cn/problem/P1638

题目描述

博览馆展出 m 位画家的作品,游客需要购买门票观看第 a 幅到第 b 幅画(门票价格为 b-a+1 元)。要求入场后能看到所有 m 位画家的作品,求最小的门票价格(即包含所有画家作品的最短子数组长度)。若有多个解,输出 a 最小的那组。

输入:第一行两个整数 n(图画总数)和 m(画家数量),第二行 n 个整数表示每幅画的画家编号(1≤a_i≤m≤2e3)。

输出:一行两个整数 a 和 b(子数组的左右边界,从 1 开始计数)。

题目分析

这道题的核心需求是 "找到包含所有 m 个不同元素的最短连续子数组",属于 "求最短" 类型的滑动窗口问题。和上一道 "求最长" 的题不同,这道题的判断条件是 "窗口内是否包含所有 m 个元素",当满足条件时,需要尝试收缩左边界以找到更短的子数组。

暴力解法(超时警告)

枚举所有子数组,判断是否包含所有 m 个元素,记录最短长度。时间复杂度 O (n²),n=1e6 时超时。

双指针优化思路

  • leftright表示当前窗口的左右边界,初始值为 1(因为题目要求从 1 开始计数)。
  • 数组 mp记录窗口内每个画家作品的出现次数,用 kind记录窗口内不同画家的数量。
  • right向右移动,将当前画作加入窗口:
    • 如果 mp [a [right]] 从 0 变为 1,说明窗口内新增了一个画家,kind++
    • kind == m 时,说明窗口内包含所有画家的作品,此时需要收缩左边界,尝试找到更短的子数组:
      • 记录当前窗口长度,若比当前最短长度更短,更新结果。
      • 移动 left,将左边界的画作移出窗口:如果 mp [a [left]] 从 1 变为 0,kind--
  • 重复上述过程,直到 right遍历完数组。

代码实现

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1e6 + 10;
const int M = 2e3 + 10;
int a[N];
int mp[M]; // 统计每个画家作品的出现次数

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    int left = 1;
    int kind = 0; // 窗口内不同画家的数量
    int min_len = n; // 初始化为最大可能长度
    int begin = 1; // 结果的起始位置
    for (int right = 1; right <= n; right++) {
        // 进窗口:新增当前画作
        if (mp[a[right]]++ == 0) {
            kind++;
        }
        // 判条件:窗口内包含所有画家,尝试收缩左边界
        while (kind == m) {
            // 更结果:更新最短长度和起始位置
            int current_len = right - left + 1;
            if (current_len < min_len) {
                min_len = current_len;
                begin = left;
            }
            // 出窗口:收缩左边界
            if (mp[a[left]]-- == 1) {
                kind--;
            }
            left++;
        }
    }
    // 输出结果(起始位置和结束位置)
    cout << begin << " " << begin + min_len - 1 << endl;
    return 0;
}

思路总结

这道题的关键是**"满足条件后立即收缩窗口"**。因为我们要找的是最短子数组,当窗口已经包含所有 m 个元素时,继续向右移动 right 只会让窗口更长,所以需要收缩左边界来优化。同时,由于画家编号的范围较小(m≤2e3),我们可以用数组代替哈希表,提高运行效率。

例题 3:字符串(牛客网)------ 包含所有小写字母的最短子串

题目链接: https://ac.nowcoder.com/acm/problem/18386

题目描述

给定一个只包含小写字母的字符串 S,找出所有包含所有 26 个小写字母的合法子串中,长度最短的那个。

输入:一行一个字符串 S(长度不超过 1e6)。

输出:一行一个整数,表示最短合法子串的长度。

题目分析

这道题其实是上一道 "逛画展" 的变种,只是将 "m 个画家" 换成了 "26 个小写字母",核心思路完全一致。属于**"包含所有目标元素的最短子串"**问题,是滑动窗口的经典应用场景。

双指针优化思路

  • leftright表示当前窗口的左右边界,初始值为 0。
  • 数组 mp 记录窗口内每个小写字母的出现次数,用 kind记录窗口内不同字母的数量。
  • right向右移动,将当前字符加入窗口:
    • 如果 **mp [s [right]-'a']**从 0 变为 1,kind++
    • kind == 26 时,说明窗口内包含所有小写字母,收缩左边界以找到更短的子串:
      • 更新最短长度。
      • 移动 left,将左边界字符移出窗口:若 mp [s [left]-'a'] 从 1 变为 0,kind--
  • 重复上述过程,直到 right遍历完字符串。

代码实现

cpp 复制代码
#include <iostream>
#include <string>
#include <climits>
using namespace std;

int mp[26]; // 统计每个小写字母的出现次数

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    string s;
    cin >> s;
    int n = s.size();
    int left = 0;
    int kind = 0;
    int min_len = INT_MAX;
    for (int right = 0; right < n; right++) {
        // 进窗口:新增当前字符
        if (mp[s[right] - 'a']++ == 0) {
            kind++;
        }
        // 判条件:包含所有26个字母,收缩左边界
        while (kind == 26) {
            // 更结果:更新最短长度
            min_len = min(min_len, right - left + 1);
            // 出窗口:收缩左边界
            if (mp[s[left] - 'a']-- == 1) {
                kind--;
            }
            left++;
        }
    }
    cout << min_len << endl;
    return 0;
}

思路总结

这道题和 "逛画展" 的核心逻辑完全一致,只是目标元素从 "m 个画家" 固定为 "26 个小写字母"。通过这道题可以发现,滑动窗口问题具有很强的通用性 ------ 只要是 "包含所有目标元素的最短子串" 或 "包含所有目标元素的最长子串" 问题,都可以用类似的思路解决。关键是要明确 "目标元素是什么"、"如何判断窗口是否包含所有目标元素"。

例题 4:丢手绢(牛客网)------ 环形数组的最短距离

题目链接: https://ac.nowcoder.com/acm/problem/207040

题目描述

小朋友们围成一个圆圈玩丢手绢游戏,每个小朋友之间的顺时针距离已知。定义两个小朋友的距离为顺时针或逆时针走的最近距离,求离得最远的两个小朋友的距离(即最大的最近距离)。

输入:第一行一个整数 N(小朋友数量),接下来 N 行每行一个整数,表示第 i-1 个小朋友顺时针到第 i 个小朋友的距离(最后一行是第 N 个小朋友顺时针到第一个小朋友的距离)。

输出:一个整数,表示最大的最近距离。

题目分析

这道题的难点在于**"环形数组"**------ 小朋友围成一个圆圈,因此任意两个小朋友之间有两条路径(顺时针和逆时针),距离取较短的那个。我们需要找到所有小朋友对中,这个 "较短距离" 的最大值。

首先,整个圆圈的总长度 sum是固定的,对于任意一段顺时针距离 k,对应的逆时针距离是 sum - k,因此最近距离是 min (k, sum - k)。我们的目标是找到 max (min (k, sum - k)),其中 k 是任意两个小朋友之间的顺时针距离。

暴力解法(超时警告)

枚举所有可能的顺时针距离 k,计算min (k, sum - k),记录最大值。时间复杂度 O (n²),n=1e5 时超时。

双指针优化思路

由于小朋友围成一个圆圈,我们可以将环形数组展开为线性数组(复制一份拼接在后面),但这样会增加空间复杂度。更高效的方法是利用双指针维护一个**"环形窗口"**:

  • leftright表示当前顺时针路径的起点和终点,初始值为 1。
  • k记录当前路径的顺时针距离之和,sum记录整个圆圈的总长度。
  • right向右移动,累加当前距离到 k
    • 2*k >= sum 时,说明当前路径的顺时针距离 k已经大于等于逆时针距离 sum - k,此时 min (k, sum - k) = sum - k。继续向右移动 right 会让 k 更大,sum - k 更小,因此不需要再移动 right,转而收缩 left。
    • 每次移动后,更新最大的最近距离(取 ksum - k 中的较小值,再与当前最大值比较)。
  • 重复上述过程,直到 right 遍历完所有小朋友。

代码实现

cpp 复制代码
#include <iostream>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
LL a[N]; // 存储每个小朋友之间的顺时针距离

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    LL sum = 0;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum += a[i];
    }
    int left = 1;
    LL k = 0;
    LL max_dist = 0;
    for (int right = 1; right <= n; right++) {
        // 进窗口:累加当前距离
        k += a[right];
        // 判条件:顺时针距离 >= 逆时针距离,收缩左边界
        while (2 * k >= sum) {
            // 更结果:更新最大最近距离
            max_dist = max(max_dist, sum - k);
            // 出窗口:收缩左边界
            k -= a[left++];
        }
        // 更结果:更新最大最近距离(顺时针距离 < 逆时针距离的情况)
        max_dist = max(max_dist, k);
    }
    cout << max_dist << endl;
    return 0;
}

思路总结

这道题的关键是利用环形数组的特性,将问题转化为 "维护一段顺时针路径,找到最大的 min (k, sum - k)"。双指针的核心逻辑是**"当顺时针距离超过总长度的一半时,收缩左边界"** ,因为此时逆时针距离更短,继续扩展右边界只会让逆时针距离更小。通过这种方式,left 和 right 都只遍历一次数组,时间复杂度是 O (n)

五、双指针算法的常见误区与避坑指南

5.1 指针回退

双指针的核心优势是 "指针不回退",如果在代码中出现 left 向左移动的情况,大概率是思路出错了。此时需要重新审视判断条件,确保指针只会同向移动。

5.2 辅助数据结构选择不当

在处理大规模数据时,辅助数据结构的效率会直接影响算法性能。例如:

  • 当目标元素的范围较小时(如例题 2 中的画家编号≤2e3),用数组代替哈希表可以提高访问速度。
  • 当目标元素的范围较大时(如例题 1 中的雪花编号≤1e9),必须用哈希表(unordered_map)存储计数。

5.3 边界条件处理不当

  • 数组或字符串的下标是否从 0 开始或从 1 开始(如例题 2 要求从 1 开始计数)。
  • 当所有元素都满足条件时,是否会遗漏最小编号的解(如例题 2 要求 a 最小)。
  • 环形数组的边界处理(如例题 4 中 right 遍历到 n 后是否需要重新开始)。

5.4 忘记优化输入输出

当 n=1e6 时,使用 cin 和 cout 默认的输入输出速度会很慢,导致超时。因此,在处理大规模数据时,可以加上下面两句代码:

cpp 复制代码
ios::sync_with_stdio(false);
cin.tie(0);

这两行代码可以关闭 cin 和 cout 与 stdio 的同步,大幅提高输入输出速度。

六、双指针算法的拓展应用场景

除了上述例题中的场景,双指针还可以应用于以下问题:

  1. 两数之和 / 三数之和 / 四数之和:反向双指针,从数组两端向中间移动,降低时间复杂度。
  2. 数组反转:反向双指针,交换两端元素,直到指针相遇。
  3. 快慢指针找链表中点 / 环:同向双指针,快指针每次走两步,慢指针每次走一步。
  4. 合并两个有序数组:同向双指针,分别指向两个数组的起始位置,比较后合并。
  5. 滑动窗口求和:求子数组和等于目标值的最长 / 最短子数组。

这些问题的核心思路都是 "通过两个指针的协同移动,优化暴力枚举的时间复杂度",只要掌握了双指针的核心思想和通用模板,都能轻松解决。


总结

双指针算法虽然简单,但却蕴含着 "化繁为简" 的算法思想。它告诉我们:有时候,解决复杂问题不需要复杂的代码,只需要换一种思路,用更巧妙的方式组织遍历逻辑。希望通过本文的讲解,你能真正吃透双指针算法,在未来的算法竞赛和笔试中,用它来解决更多问题。

最后,送给大家一句话:算法的本质是逻辑的优化,而双指针,正是这种优化思想的最佳体现。祝大家在算法学习的道路上,一路披荆斩棘,不断突破自我!

相关推荐
cs麦子2 小时前
C语言--详解--指针--下
c语言·数据结构·算法
Tisfy2 小时前
LeetCode 2536.子矩阵元素加 1:二维差分数组
算法·leetcode·矩阵
北邮刘老师3 小时前
智能家居,需要的是“主控智能体”而不是“主控节点”
人工智能·算法·机器学习·智能体·智能体互联网
oioihoii3 小时前
C++中有双向映射数据结构吗?Key和Value能否双向查找?
数据结构·c++·算法
nnn__nnn3 小时前
图像分割技术全解析:从传统算法到深度学习的视觉分割革命
深度学习·算法·计算机视觉
_OP_CHEN3 小时前
算法基础篇:(八)贪心算法之简单贪心:从直觉到逻辑的实战指南
c++·算法·贪心算法·蓝桥杯·算法竞赛·acm/icpc·简单贪心
小欣加油3 小时前
leetcode 2536 子矩阵元素加1
数据结构·c++·算法·leetcode·矩阵
橘颂TA3 小时前
【剑斩OFFER】算法的暴力美学——二维前缀和
算法·c/c++·结构与算法
月半流苏3 小时前
Problem: lab-week10-exercise02 Building a Fiber Network
c++·算法·并查集