<算法学习之深入浅出双指针>
相信双指针法对大多数人来说并不陌生。但它并不属于某一数据结构。
在数组,链表,字符串当中都有所应用。所以我认为需要对双指针法
进行一次总结学习。
一、数组篇
1、引入
提出一个很简单的问题:就地移除一位元素。很难吗?当然不,你会说我们只需要遍历然后把它找出来
就可以了。那么我们先来看一组对比:
for (int i = 0; i < array.size(); i++) {
if (array[i] == target) {
array.erase(i);
}
}
看上去时间复杂度是O(n)对吗?事实上是O(n^2),因为erase本身也是O(n)操作。这时才体现出双指针的优势:
通过两个指针在一次for循环下完成两次for循环的操作。此处应用了快慢指针的解法。
int slow = 0;
for (int fast = 0; fast < nums.size(); fast++) {
if (val != nums[fast]) {
nums[slowIndex++] = nums[fastIndex];
}
}
2、例题说明(初级版)
(本题来自LeetCode167题 easy)
题目描述
在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
输入输出样例
输入是一个数组(numbers)和一个给定值(target)。输出是两个数的位置,从 1 开始计数。
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
在这个样例中,第一个数字(2)和第二个数字(7)的和等于给定值(9)。
题解:已知数组是排好序的,我们可以采用方向相反的两个指针遍历数组,当两个指针的指向的元素和>target时,左移右指针,反之,右移左指针。(具体证明暂未给出)
vector<int> twoSum(vector<int>& numbers, int target) {
int l = 0, r = numbers.size() - 1, sum;
while (l < r) {
sum = numbers[l] + numbers[r];
if (sum == target) break;
if (sum < target) ++l;
else --r;
}
return vector<int>{l + 1, r + 1};
}
触类旁通:请尝试leetcode633题。
3、进阶版——滑动窗口法
(本题来自LeetCode76 hard)
题目描述
给定两个字符串 S 和T,求 S 中包含T 所有字符的最短连续子字符串的长度,同时要求时间
复杂度不得超过 O(n)。
输入输出样例
输入是两个字符串 S 和 T,输出是一个 S 字符串的子串。
Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
在这个样例中,S 中同时包含一个 A、一个 B、一个 C 的最短子字符串是“BANC”。
题解
本题使用滑动窗口法求解,即两个指针l和r都是从左往右移动,但l始终位于r的左侧或重合。先统计并保存T当中的字符情况,然后不断移动r遍历S,先flag数组判断S[i]是否位于T当中及其个数,再更改统计数据。保证S当中的字串完全包含T的前提下,尝试右移l指针获得最短字符串。本题使用了长度为128的数组来映射字符。也可以使用哈希表代替。
string minWindow(string S, string T) {
vector<int> chars(128, 0);
vector<bool> flag(128, false);
// 先统计T中的字符情况
for (int i = 0; i < T.size(); ++i) {
flag[T[i]] = true;
++chars[T[i]];
}
// 移动滑动窗口,不断更改统计数据
int cnt = 0, l = 0, min_l = 0, min_size = S.size() + 1;
for (int r = 0; r < S.size(); ++r) {
if (flag[S[r]]) {
if (--chars[S[r]]){
++cnt;
}
// 若目前滑动窗口已包含T中全部字符,
// 则尝试将l右移,在不影响结果的情况下获得最短子字符串
while (cnt == T.size()) {
if (r - l + 1 < min_size) {
min_l = l;//更改左边界
min_size = r - l + 1;//更改最小子串长度
}
if (flag[S[l]] && ++chars[S[l]] > 0) {//至少是0
--cnt;
}
++l;//确认无误后右移l
}
}
}
return min_size > S.size()? "": S.substr(min_l, min_size);
}