gh_mirrors/leet/leetcode项目:双指针技巧在题解中的应用
在解决LeetCode算法问题时,我们经常会遇到需要优化时间复杂度的场景。双指针(Two Pointers)技巧作为一种高效的解题思路,能够帮助我们将嵌套循环的O(n²)复杂度降低到O(n),尤其适用于数组和字符串处理。本文将结合gh_mirrors/leet/leetcode项目中的题解,详细介绍双指针技巧的应用场景与实现方法。
一、双指针技巧的核心原理
双指针技巧通过使用两个指针(索引)在数据结构中移动,协同完成遍历或查找任务。根据指针移动方向,可分为两类:
- 同向指针:两个指针朝同一方向移动,如滑动窗口问题
- 相向指针:两个指针从两端向中间移动,如二分查找、回文判断
项目中C++/chapImplement.tex文件明确指出:"双指针,动态维护一个区间。尾指针不断往后扫,当扫到有一个窗口包含了所有T的字符后,然后再收缩头指针,直到不能再收缩为止。"这种滑动窗口思想是双指针技巧的典型应用。
二、滑动窗口:最小覆盖子串问题
问题描述
给定字符串S和T,在S中找出包含T所有字符的最小子串,时间复杂度要求O(n)。
双指针实现思路
- 使用左右两个指针表示窗口的边界
- 右指针不断扩展,直到窗口包含T的所有字符
- 左指针不断收缩,直到窗口不再包含T的所有字符
- 记录最小窗口长度
代码实现
// LeetCode, Minimum Window Substring
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
string minWindow(string S, string T) {
if (S.empty()) return "";
if (S.size() < T.size()) return "";
const int ASCII_MAX = 256;
int appeared_count[ASCII_MAX];
int expected_count[ASCII_MAX];
fill(appeared_count, appeared_count + ASCII_MAX, 0);
fill(expected_count, expected_count + ASCII_MAX, 0);
for (size_t i = 0; i < T.size(); i++) expected_count[T[i]]++;
int minWidth = INT_MAX, min_start = 0; // 窗口大小,起点
int wnd_start = 0;
int appeared = 0; // 完整包含了一个T
//尾指针不断往后扫
for (size_t wnd_end = 0; wnd_end < S.size(); wnd_end++) {
if (expected_count[S[wnd_end]] > 0) { // this char is a part of T
appeared_count[S[wnd_end]]++;
if (appeared_count[S[wnd_end]] <= expected_count[S[wnd_end]])
appeared++;
}
if (appeared == T.size()) { // 完整包含了一个T
// 收缩头指针
while (appeared_count[S[wnd_start]] > expected_count[S[wnd_start]]
|| expected_count[S[wnd_start]] == 0) {
appeared_count[S[wnd_start]]--;
wnd_start++;
}
if (minWidth > (wnd_end - wnd_start + 1)) {
minWidth = wnd_end - wnd_start + 1;
min_start = wnd_start;
}
}
}
if (minWidth == INT_MAX) return "";
else return S.substr(min_start, minWidth);
}
};
三、区间合并:插入区间问题
问题描述
给定一组非重叠区间,插入一个新区间并合并所有重叠区间。
双指针实现思路
- 遍历区间列表,找到新区间需要插入的位置
- 合并所有与新区间重叠的区间
- 维护一个结果列表,用指针记录当前位置
代码实现
struct Interval {
int start;
int end;
Interval() : start(0), end(0) { }
Interval(int s, int e) : start(s), end(e) { }
};
//LeetCode, Insert Interval
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
vector<Interval> insert(vector<Interval> &intervals, Interval newInterval) {
vector<Interval>::iterator it = intervals.begin();
while (it != intervals.end()) {
if (newInterval.end < it->start) {
intervals.insert(it, newInterval);
return intervals;
} else if (newInterval.start > it->end) {
it++;
continue;
} else {
newInterval.start = min(newInterval.start, it->start);
newInterval.end = max(newInterval.end, it->end);
it = intervals.erase(it);
}
}
intervals.insert(intervals.end(), newInterval);
return intervals;
}
};
四、双指针技巧的典型应用场景
根据项目题解分析,双指针技巧主要应用于以下场景:
| 应用场景 | 典型问题 | 时间复杂度优化 |
|---|---|---|
| 滑动窗口 | 最小覆盖子串、最长无重复子串 | O(n²)→O(n) |
| 区间合并 | 插入区间、合并区间 | O(n) |
| 数组排序 | 颜色分类、有序数组去重 | O(n) |
| 链表操作 | 检测环、寻找中点 | O(n) |
项目中C++/chapSorting.tex明确提到:"// 双指针,时间复杂度O(n),空间复杂度O(1)",印证了双指针在排序问题中的高效性。
五、实战技巧与注意事项
- 指针初始化:根据问题类型选择合适的起始位置
- 循环条件:注意避免越界,明确指针移动终止条件
- 移动策略:同向指针注重窗口大小调整,相向指针注重边界条件判断
- 复杂度分析:双指针通常能将时间复杂度降低一个数量级
六、总结与扩展学习
双指针技巧是解决数组、字符串类问题的利器,通过gh_mirrors/leet/leetcode项目中的题解可以看出,掌握这一技巧能显著提升解题效率。建议进一步学习:
- C++/chapImplement.tex中的细节实现题
- C++/chapSearching.tex中的搜索类问题
- C++/chapString.tex中的字符串处理技巧
通过持续练习,你将能够灵活运用双指针技巧解决各类复杂问题,大幅提升算法效率与代码质量。
欢迎点赞收藏本教程,关注项目获取更多算法解题技巧!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



