深入浅出双指针法——数组篇

<算法学习之深入浅出双指针>

相信双指针法对大多数人来说并不陌生。但它并不属于某一数据结构。

在数组,链表,字符串当中都有所应用。所以我认为需要对双指针法

进行一次总结学习。

一、数组篇
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);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值