Skip to content

Latest commit

 

History

History
159 lines (109 loc) · 8.47 KB

双指针.md

File metadata and controls

159 lines (109 loc) · 8.47 KB

双指针 | 滑动窗口

0. 概念

顾名思义,双指针就是左右指针同时枚举,有点类似于滑动窗口

可以这样辨别一下:窗口大小变化的是双指针,窗口大小固定的是滑动窗口

但是基本上可以不用区分,叫啥都可以,本质是一样的

1. 例题

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

  • 枚举右端点
  • 哈希表记录当前字符出现个数
int lengthOfLongestSubstring(string s) {
    int n = s.size(), ans = 0;
    int left = 0, right = 0;
    unordered_map<char, int> mp; // 维护字符出现次数
    
    for (right = 0; right < n; ++ right) {
        char c = s[right];
        mp[c] += 1;
        
        // 当前字符出现次数多余 1 次就需要右移左端点直到次数为 1 次
        while (mp[c] > 1) {
            -- mp[s[left++]];
        }
        ans = max(ans, right-left+1);
    }
    return ans;
}

其实哈希表可以直接记录当前字符最后出现的位置,这样避免一直移动左端点,可以节省时间

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

  • 枚举右端点 right,维护 [left, right] 之间的元素和
  • 如果元素和大于 target,就右移左端点
int minSubArrayLen(int target, vector<int>& nums) {
    int n = nums.size();
    int left = 0, right = 0;
    int sum = 0;
    int ans = n + 1;	// INT_MAX
    for (right = 0; right < n; ++ right) {
        sum += nums[right];
        /* while (sum - nums[left] >= target) {
            sum -= nums[left];
            ++ left;
        }
        if (s >= target) ans = min(ans, right-left+1); */
        // 比较好理解的写法:直接在条件的区间里面计算 ans
        while (sum >= target) {
            ans = min(ans, right-left+1);
            sum -= nums[left];
            ++ left;
        }
    }
    return ans;
}

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

  • 和前一题同理,枚举右端点,维护区间元素的乘积
  • 注意边界情况以及 k=1 的特判
int numSubarrayProductLessThanK(vector<int>& nums, int k) {
	int n = nums.size();
    int left = 0, right = 0;
    int ans = 0;
    int m = 1;	// 维护 [left, right] 各个元素的乘积
    
    if (k <= 1) return 0;   // 特判, [1,1,1] k=1
    
    for (right = 0; right < n; ++ right) {
        m *= nums[right];
        while (m >= k) {
            m /= nums[left];
            ++ left;
        }
        // 以 right 结尾的子数组中 m < k 的个数为 right-left+1
        // [left, right] [left+1, right] [right, right]
        ans += right - left + 1;
    }
    return ans;
}

2. 相关题目

题目 说明 题解
76. 最小覆盖子串 通过哈希表 needHash 判断滑动窗口包含了T的所有元素,使用 needCnt 免除每次遍历哈希表 🔥 滑窗
395. 至少有 K 个重复字符的最长子串 直接双指针无法解决,加入枚举目标串字符种类数 [1, 26]条件 就可以让区间具有二段性 🔥,另外可以用分治递归求解 宫水三叶
1004. 最大连续1的个数 III 维护区间 0 的个数,找出一个最长的子数组,该子数组内最多允许有 K 个 0(正难则反 负雪明烛
1234. 替换子串得到平衡字符串 待替换子串之外的任意字符的出现次数都不能超过 m = n/4,维护区间外的字符出现次数 0x3F
1438. 绝对差不超过限制的最长连续子数组 维护区间内最大值和最小值的差值,使用底层是红黑树的有序集合 multiset 负雪明烛
1658. 将 x 减到 0 的最小操作数 和 2516 类似,逆向思考更直观,正向思考须分前缀后缀考虑 0x3F
2379. 得到 K 个黑块的最少涂色次数 最基本的滑动窗口,窗口大小固定 通过
2516. 每种字符至少取 K 个 正向思考需要先考虑前缀为空,然后枚举前缀,反向思考更容易理解,直接滑动窗口取中间的字符至多x个 双指针 滑窗

总结

  • 双指针的题目需要知道维护什么,比如维护区间的元素和、元素乘积或者0的个数
  • 有时候正向难考虑就可以逆向思考,正难则反
  • 有时根据题目条件无法沟站区间的二段性,这时候可以考虑加入隐含条件,让区间具有二段性,例如 LC.395
  • 有些题目难以找到左边界右移的条件,例如 LC.76,需要两层思考:窗口是否满足要求以及如何判断

其他

题目 说明 题解
6355. 统计公平数对的数目 排序之后,维护双指针出和的大小 题解

这里题目和一般的滑动窗口不太一样,就是单纯的双指针,两个指针一个在左端,一个在右端,然后两个指针向中点移动。

3. 参考