❗⭕⭕算法子串链表的好伴侣——滑动窗口双指针

加个封面
在这里插入图片描述

1 问什么用双指针,不用dp?

  • 对于数组子串这种连续问题解,优先应考虑双指针。
  • 没有明显的状态转移,前后的依赖关系,考虑双指针解法。
  • 至于不连续的子问题解,即为子序列问题,一般使用动态规划来解决,要求更为苛刻,同时也能解决一些子串问题,详情见另一篇博客
  • 能用双指针不用动态规划,时间复杂度放那了,比如最长回文子序列最长回文子串。子序列得用动态规划,子串可以用双指针和动态规划,明显双指针更优。
  • 本文借鉴于网上资料,再加个人见解。

2 基本框架

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

3 子串例题

3.1 最小覆盖子串

leetcode76问题
在这里插入图片描述

分析

  • right扩大至windows满足条件,即windows包含三个T中的字母,left缩小windows至刚好满足条件,找到一个字串,记录长度。
  • left再缩小windows会破坏满足条件,right就会扩大,再找另一个满足条件的windows
  • 具体看代码中的注释

代码

string minWindow(string s, string t) {
//need被需要存的map,window滑动窗口中存的map
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
//window中存的need中的有效字母个数
    int valid = 0;
    // 记录最小覆盖子串的起始索引及长度
    int start = 0, len = INT_MAX;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        //need中存在该字母,则记录到window中,如果该字母个数已经集满,有效值字母数加1
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        //有效字母数和need中的一致时,满足条件
        while (valid == need.size()) {
            // 在这里更新最小覆盖子串
            //记录最小长度和start点,便于返回
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            //弹出左值
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            //弹出时,是need中的字母,那就更新window中的该字母的个数和总的有效字母数。
            //valid的变化会使跳出while,右边界又开始找新的值了
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }                    
        }
    }
    // 返回最小覆盖子串
    return len == INT_MAX ?
        "" : s.substr(start, len);
}

3.2 字符串排序

leetcode567问题
在这里插入图片描述
分析

  • 排列必须是连续的。
  • window的长度必须保持和字串的长度一致,思想和上一题基本一致。
  • 不同点在于窗口收缩的时机判断。

代码

// 判断 s 中是否存在 t 的排列
bool checkInclusion(string t, string s) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
            //有效的字母总个数
                valid++;
        }

        // 判断左侧窗口是否要收缩
        //窗口中存放的数字大于子串就要收缩
        while (right - left >= t.size()) {
            // 在这里判断是否找到了合法的子串
            if (valid == need.size())
                return true;
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    // 未找到符合条件的子串
    return false;
}

3.3 找所有字母的异位词

leetcode438问题
在这里插入图片描述
分析

  • 找到子排列数组后记录起始点就可。
  • 找子排列就是上面一题。

代码

vector<int> findAnagrams(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    vector<int> res; // 记录结果
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) 
                valid++;
        }
        // 判断左侧窗口是否要收缩
        while (right - left >= t.size()) {
            // 当窗口符合条件时,把起始索引加入 res
            if (valid == need.size())
                res.push_back(left);
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    return res;
}

3.4 最长无重复子串

leetcode3问题
在这里插入图片描述

分析

  • 无重复时候right扩张
  • 有重复时候left收缩,
  • 记录最大值

代码

int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> window;

    int left = 0, right = 0;
    int res = 0; // 记录结果
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        window[c]++;
        // 判断左侧窗口是否要收缩
        while (window[c] > 1) {
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            window[d]--;
        }
        // 在这里更新答案
        res = max(res, right - left);
    }
    return res;
}

4 链表中的双指针

链表相关博文:⛔⛔⛔数据结构——链链链链表表表表

4.1成环

4.2 成环入口

4.3 三等分点

4.4链表倒数第K个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zkFun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值