【每日算法】Day 3-1:双指针算法终极指南——6大应用场景与高频题解析(C++实现)

攻克算法面试必考题型!今日系统解析双指针算法的核心思想与高频应用场景,覆盖数组、链表、字符串等经典问题,助你掌握O(n)高效解法。

一、双指针算法核心思想

双指针算法通过维护两个指针协同遍历数据结构,将暴力解法的时间复杂度从O(n²)优化至O(n),核心策略:

  1. 缩减搜索空间:通过指针移动排除无效区间

  2. 利用单调性:基于数据有序性减少重复计算

  3. 同步/异步移动:根据问题需求设计指针移动逻辑

适用场景特征:

  • 线性数据结构(数组、链表、字符串)

  • 存在明显的位置关联性(如对称性、区间特性)

  • 可分解为两两关系的问题(如两数之和、三数之和)


二、六大应用场景详解

场景1:相向双指针(对撞指针)

应用方向:

  • 有序数组两数之和

  • 盛水最多的容器

  • 回文字符串验证

代码模板:

int left = 0, right = n-1;
while (left < right) {
    if (满足条件) return 结果;
    else if (条件分支1) left++;
    else right--;
}

真题示例(LeetCode 15. 三数之和):

vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    int n = nums.size();
    for (int i=0; i<n-2; ++i) {
        if (i>0 && nums[i]==nums[i-1]) continue;
        int left = i+1, right = n-1;
        while (left < right) {
            int sum = nums[i]+nums[left]+nums[right];
            if (sum == 0) {
                res.push_back({nums[i], nums[left], nums[right]});
                while (left<right && nums[left]==nums[left+1]) left++;
                while (left<right && nums[right]==nums[right-1]) right--;
                left++; right--;
            } 
            else if (sum < 0) left++;
            else right--;
        }
    }
    return res;
}
场景2:快慢双指针(龟兔赛跑)

应用方向:

  • 链表环检测(Floyd判圈算法)

  • 链表中点定位

  • 数组去重/元素迁移

代码模板:

int slow = 0, fast = 0;
while (fast < n) {
    if (满足条件) {
        swap(arr[slow], arr[fast]);
        slow++;
    }
    fast++;
}

真题示例(LeetCode 26. 有序数组去重):

int removeDuplicates(vector<int>& nums) {
    if (nums.empty()) return 0;
    int slow = 0;
    for (int fast=1; fast<nums.size(); ++fast) {
        if (nums[fast] != nums[slow]) {
            nums[++slow] = nums[fast];
        }
    }
    return slow + 1;
}
场景3:滑动窗口(同向双指针)

应用方向:

  • 最长无重复子串

  • 最小覆盖子串

  • 区间最大值/平均值

代码模板:

int left = 0, right = 0;
while (right < n) {
    窗口扩展:将nums[right]加入窗口
    while (窗口不满足条件) {
        窗口收缩:移除nums[left]
        left++;
    }
    更新最优解
    right++;
}

真题示例(LeetCode 3. 最长无重复子串):

int lengthOfLongestSubstring(string s) {
    unordered_set<char> window;
    int left=0, max_len=0;
    for(int right=0; right<s.size(); ++right){
        while(window.count(s[right])){
            window.erase(s[left]);
            left++;
        }
        window.insert(s[right]);
        max_len = max(max_len, right-left+1);
    }
    return max_len;
}
场景4:分离双指针(多序列处理)

应用方向:

  • 合并有序数组

  • 判断子序列

  • 区间列表交集

真题示例(LeetCode 88. 合并有序数组):

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    int p1 = m-1, p2 = n-1, p = m+n-1;
    while (p1 >=0 && p2 >=0) {
        nums1[p--] = (nums1[p1] > nums2[p2]) ? nums1[p1--] : nums2[p2--];
    }
    while (p2 >=0) nums1[p--] = nums2[p2--];
}
场景5:链表的双指针操作

应用方向:

  • 链表倒数第k个节点

  • 链表翻转

  • 链表相交判断

真题示例(剑指 Offer 22. 链表中倒数第k个节点):

ListNode* getKthFromEnd(ListNode* head, int k) {
    ListNode *fast = head, *slow = head;
    for(int i=0; i<k; ++i) fast = fast->next;
    while(fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}
场景6:数学特性双指针

应用方向:

  • 平方数判断

  • 丑数生成

  • 数值逼近问题

真题示例(LeetCode 633. 平方数之和):

bool judgeSquareSum(int c) {
    long left=0, right=sqrt(c);
    while(left <= right){
        long sum = left*left + right*right;
        if(sum == c) return true;
        else if(sum < c) left++;
        else right--;
    }
    return false;
}

三、复杂度与优化技巧

场景类型时间复杂度空间复杂度优化要点
相向双指针O(n)O(1)利用数据有序性
快慢指针O(n)O(1)避免多余空间使用
滑动窗口O(n)O(k)哈希表替代暴力统计
分离双指针O(m+n)O(1)逆向遍历避免覆盖

四、大厂真题实战

真题1:接雨水问题

题目描述:
给定表示高度的数组,计算下雨后能接多少雨水

双指针解法:

int trap(vector<int>& height) {
    int left=0, right=height.size()-1;
    int left_max=0, right_max=0, res=0;
    while(left < right) {
        left_max = max(left_max, height[left]);
        right_max = max(right_max, height[right]);
        if(height[left] < height[right]) {
            res += left_max - height[left];
            left++;
        } else {
            res += right_max - height[right];
            right--;
        }
    }
    return res;
}
真题2:字符串排列检查

题目描述:
判断字符串s2是否包含s1的某个排列的子串

滑动窗口解法:

bool checkInclusion(string s1, string s2) {
    vector<int> cnt(26,0), window(26,0);
    for(char c : s1) cnt[c-'a']++;
    int left=0, n=s2.size(), k=s1.size();
    for(int right=0; right<n; ++right){
        window[s2[right]-'a']++;
        if(right-left+1 == k) {
            if(window == cnt) return true;
            window[s2[left++]-'a']--;
        }
    }
    return false;
}

、常见误区与避坑指南

  1. 指针越界:未检查指针移动后的有效性(如访问nums[right+1]导致越界)

  2. 条件遗漏:滑动窗口收缩条件不完整(如while误写为if

  3. 顺序依赖:未考虑数据有序性前提(如两数之和未排序直接使用对撞指针)

  4. 更新滞后:先移动指针再计算结果导致漏判

  5. 死循环陷阱:指针未在条件分支中正确移动


六、总结与拓展

核心能力提升:

  • 问题转化能力(将复杂问题抽象为双指针模型)

  • 边界处理能力(精确控制指针移动条件)

  • 模板活用能力(根据场景选择最优指针策略)

进阶思考:

  1. 如何用双指针解决四数之和问题?

  2. 滑动窗口如何应用于数据流场景?

  3. 快慢指针在检测链表环时的数学原理是什么?


LeetCode真题训练:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值