滑动窗口

滑动窗口使用前提:

  • 连续子数组。
  • 有单调性。本题元素均为正数,这意味着只要某个子数组满足题目要求,在该子数组内的更短的子数组同样也满足题目要求。

一、不定长滑动窗口(求最短/最小)

209.长度最小的子数组

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size(), sum = 0;
        int l = 0, r = 0;
        int ans = 0x3f3f3f3f;
        while (r < n) {
            sum += nums[r];
            while (sum >= target) {//满足要求就更新 ans,再移动左指针
                ans = min(ans, r - l + 1);
                sum -= nums[l];
                l++;
            }
            r++;
        }
        return ans <= n ? ans : 0;
    }
};

另一种写法:
(需要保证循环后 \(\rm sum\) 仍然大于 \(\rm target\),但如果循环之前 \(\rm sum < target\) 就不会进入循环,循环后还需要 \(\rm if\) 判断一下是否满足 \(\rm sum \geqslant target\)。)

class Solution {
public:
    int minSubArrayLen(int target, vector<int> &nums) {
        int n = nums.size(), ans = n + 1, sum = 0, left = 0;
        for (int right = 0; right < n; right++) { // 枚举子数组右端点
            sum += nums[right];
            while (sum - nums[left] >= target) { // 尽量缩小子数组长度
                sum -= nums[left++]; // 左端点右移
            }
            if (sum >= target) {
                ans = min(ans, right - left + 1);
            }
        }
        return ans <= n ? ans : 0;
    }
};

713.乘积小于K的子数组

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k <= 1) return 0;//由于 nums[i] >= 1,乘积不可能小于1。当 k <= 1 时直接返回 0
        int n = nums.size();
        int l = 0, mul = 1, ans = 0;
        for (int r = 0; r < n; r++) {//枚举右端点,计算以每个点为右端点的子数组个数之和。
            mul *= nums[r];
            while (mul >= k) {//不符合要求,就把左端点除去
                mul /= nums[l];
                l++;
            }
            ans += r - l + 1;
        //若[l, r]满足,则[l+1, r], [l+2, r]...[r-1, r]都满足,共有 r - l + 1 个。
        }
        return ans;
    }
};

3.无重复字符的最长字串

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size(), ans = 0, l = 0;
        unordered_set<char> window; //维护从下标 l 到 r 的字符
        for (int r = l; r < n; r++) {
            char c = s[r];
            //若窗口内已经包含 c,那么再加入一个 c会导致窗口内有重复字符。所以要在加入 c 之前,先移除窗口内的 c
            while (window.count(c)) {
                window.erase(s[l++]);//缩小窗口
            }    
            window.insert(c);
            ans = max(ans, r - l + 1);
        }
        return ans;
    }
};

2958.最多K个重复元素的最长子数组

class Solution {
public:
    int maxSubarrayLength(vector<int>& nums, int k) {
        int n = nums.size(), ans = 0, l = 0;
        unordered_map<int, int> mp;
        for (int r = l; r < n; r++) {
            mp[nums[r]]++;
            while (mp[nums[r]] > k) {
                mp[nums[l]]--;
                l++;
            }
            ans = max(ans, r - l + 1);
        }
        return ans;
    }
};

2730.找到最长的半重复子字符串

class Solution {
public:
    int longestSemiRepetitiveSubstring(string s) {
        int n = s.size(), ans = 0, l = 0, cnt = 0;
        if (n == 1) return 1;//长度为1时不进入循环,直接return
        for (int r = 1; r < n; r++) {
            if (s[r - 1] == s[r]) {
                cnt++;
                if (cnt >= 2) {
                    l++;//如果当前已经s[l] == s[l-1]就不动了,l++是确保其进入新的滑窗
                    while (l < n && s[l] != s[l - 1]) l++;//不断移动左指针直到把一对相同的字符破除
                    cnt = 1;//更新cnt
                }
            }
            ans = max(ans, r - l + 1);
        }
        return ans;
    }
};

2779.数组的最大美丽值

image

image

class Solution {
public:
    int maximumBeauty(vector<int>& nums, int k) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        int l = 0, ans = 0;
        for (int r = l; r < n; r++) {
            if (nums[r] - nums[l] > 2 * k) l++;
            ans = max(ans, r - l + 1);
        }
        return ans;
    }
};

1004.最大连续1的个数

其实就是要在窗口内 \(0\) 的个数 \(\rm cnt \leqslant k\) 的条件下寻找区间长度最长的窗口。

一旦 \(\rm cnt > k\) 就不断移动左指针,直到 \(\rm cnt \leqslant k\).

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int ans = 0, n = nums.size(), cnt = 0;
        for (int l = 0, r = 0; r < n; r++) {
            cnt += 1 - nums[r];
            while (cnt > k) {
                cnt -= 1 - nums[l];
                l++;
            }
            ans = max(ans, r - l + 1);
        }
        return ans;
    }
};

2962.统计最大元素出现至少K次的子数组

若数组元素等于最大元素就把计数器++,若计数器数量 \(\geqslant k\) 就把左指针++直到窗口内最大元素数量小于 \(k\)。由于在 \(\rm [0,left-1]\) 时窗口内最大元素数量都是 \(\geqslant k\) 的,所以 \(\rm ans\) += \(\rm left\)

class Solution {
public:
    long long countSubarrays(vector<int>& nums, int k) {
        int n = nums.size();
        int m = *max_element(nums.begin(), nums.end());
        int cnt = 0, l = 0;
        long long ans = 0;
        for (int r = 0; r < n; r++) {
            if (nums[r] == m) {
                cnt++;
            } 
            while (cnt >= k) {
                cnt -= (nums[l] == m);
                l++;
            }
            ans += l;
        }
        return ans;
    }
};

2302.统计得分小于K的子数组数目

独立做出来了qwq,虽然有猜的成分~
固定子数组右端点 right,寻找满足条件的左端点 left,如果 sum(left...right) * (right - left + 1) < k,那么当 i > left 时,一定有 sum(i...right) * (right - i + 1) < k

也就是,固定子数组右端点,寻找子数组分数 < k 的最长的那个子数组 [left...right],那么比它短的所有子数组分数一定 < k,所以子数组个数是 right - left + 1 ~

class Solution {
public:
    long long countSubarrays(vector<int>& nums, long long k) {
        int n = nums.size(), l = 0;
        long long sum = 0;
        long long ans = 0;
        for (int r = 0; r < n; r++) {
            int len = r - l + 1;
            sum += nums[r];
            long long x = 1ll * len * sum;
            while (x >= k) {
                sum -= nums[l];
                len--;
                l++;
                x = 1ll * sum * len;
            }
            ans += r - l + 1;
        }
        return ans;
    }
};

1658.将x减到0的最小操作数

正难则反。
原问题等价为,从原数组中移除一个最长的子数组,使得剩余元素之和为 \(x\),也即要从原数组中找一个最长的子数组满足元素之和为 \(s-x\),其中 \(s\) 为原数组的元素之和。

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int n = nums.size(), l = 0, ans = -1;
        int m = accumulate(nums.begin(), nums.end(), 0);
        m -= x;
        int sum = 0;
        for (int r = 0; r < n; r++) {
            sum += nums[r];
            while (l < n && sum > m) {
                sum -= nums[l++];
            }
            if (sum == m) ans = max(ans, r - l + 1);
        }
        if (ans >= 0) return n - ans;
        return -1;
    }
};

76.最小覆盖字串

class Solution {
public:
    bool is_covered(int cnt_s[], int cnt_t[]) {//涵盖就是对于所有字符而言,在s中的数量都要大于等于在t中的数量
        for (int i = 'A'; i <= 'Z'; i++) {
            if (cnt_s[i] < cnt_t[i]) return false;
        }
        for (int i = 'a'; i <= 'z'; i++) {
            if (cnt_s[i] < cnt_t[i]) return false;
        }
        return true;
    }

    string minWindow(string s, string t) {
        int cnt_t[128]{}, cnt_s[128]{};//数组模拟哈希(更快)。注意这里若使用unordered_map,容器作为形参传入时需加引用,否则会拷贝一份,易TLE。
        int m = s.length();
        for (auto c : t) cnt_t[c]++;
        int l = 0;
        int ans_l = -1, ans_r = m;//ans设置为-1,若最后仍然是负数说明未被更新,返回空串。

        for (int r = 0; r < m; r++) { // 移动子串右端点
            cnt_s[s[r]]++; // 右端点字母移入子串
            while (is_covered(cnt_s, cnt_t)) { // 涵盖
                if (r - l < ans_r - ans_l) { // 找到更短的子串,满足条件就更新
                    ans_l = l; // 记录此时的左右端点
                    ans_r = r;
                }
                cnt_s[s[l++]]--; // 左指针移动到不涵盖为止
            }
        }
        return ans_l < 0 ? "" : s.substr(ans_l, ans_r - ans_l + 1);
    }
};
posted @ 2024-07-13 15:26  胖柚の工作室  阅读(29)  评论(0)    收藏  举报