不定长滑动窗口篇--罗姆的刷题记录

题单来自灵神,这里针对我的刷题过程做一个记录,本系列的目的是记录心得+做题记录的统计

2.1 越短越合法/求最长/最大

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

题意: 给一个字符串s,找出不含重复字符的最长子串长度。

code
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        unordered_map<char, int> cnt;
        int l = 0, r = 0, res = 0;
        for (r = 0; r < n; r++) {
            cnt[s[r]]++;
            if (cnt[s[r]] < 2) {
                res = max(res, r - l + 1);
            } else {
                while (cnt[s[r]] > 1)
                    cnt[s[l++]]--;
            }
        }
        return res;
    }
};
写的有点麻烦,可以调整下顺序,让代码更简单些,先去掉重复字符,在计算答案
simple code
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        unordered_map<char, int> cnt;
        int l = 0, r = 0, res = 0;
        for (r = 0; r < n; r++) {
            cnt[s[r]]++;
            while (cnt[s[r]] > 1)
                cnt[s[l++]]--;
            res = max(res, r - l + 1);
        }
        return res;
    }
};

3090. 每个字符最多出现两次的最长子字符串 1329

题意:给一个字符串s, 找出满足每个字符最多出现两次的最长子字符串,返回长度。
分析: 和第一题一样。

code
class Solution {
public:
    int maximumLengthSubstring(string s) {
        int n = s.size(), res = 0;
        unordered_map<char, int> cnt;
        int l = 0, r = 0;
        for (int r = 0; r < n; r++) {
            cnt[s[r]]++;
            while (cnt[s[r]] > 2)
                cnt[s[l++]]--;
            res = max(res, r - l + 1);
        }
        return res;
    }
};

1493. 删掉一个元素以后全为 1 的最长子数组 1423

题意:给一个二进制数组nums, 要删一个元素。删掉元素的结果数组中,返回最长的且 只包含1 的非空子数组长度。 不存在返回0
分析:删一个后只包含1, 也就是说窗口内最多一个0, 求最长窗口大小。(返回窗口大小 - 1)

code
class Solution {
public:
    int longestSubarray(vector<int>& nums) {
        int res = 0, n = nums.size();
        int l = 0, r = 0, zeroCnt = 0;
        for (int r = 0; r < n; r++) {
            if (nums[r] == 0)
                zeroCnt++;
            while (zeroCnt > 1) {
                if (nums[l] == 0)
                    zeroCnt--;
                l++;
            }
            res = max(res, r - l + 1);
        }
        return res-1;
    }
};

3634. 使数组平衡的最少移除数目 1453

题意:给一个正数组nums, 和一个整数k. 如果数组最大值至多 是最小值的k 倍, 则该数组被称为是平衡的。
可以从nums 中移除任意数量的元素,但是不能空数组。 为了让数组平衡, 需移除的最小数量。
分析: 数组排序,要么删最大,要么删最小。等价于在数组中找一个中间的区间保留,要求该区间是符合条件的。

code
class Solution {
public:
    int minRemoval(vector<int>& nums, int k) {
        int n = nums.size(), res = 1;
        sort(nums.begin(), nums.end());
        int l = 0, r = 0, cur = nums[0];
        for (int r = 1; r < n; r++) {
            while ((long long)nums[r] > (long long)cur * k) {
                cur = nums[++l];
            }
            res = max(res, r - l + 1);
        }
        return n - res;
    }
};

1208. 尽可能使字符串相等 1497

题意:两个长度相同的串 s ,t。将 s 中第 i 个字符编导t中第 i个字符,需要 |s[i]-t[i]|的开销。用于变更最大预算是 maxCost。 如果可以把s的子字符串转化成t的子字符串,返回可以转化的最大长度。 没有转化为0 。
分析:计算cost数组,然后找到最长的 满足 预算内的 区间长度。

code
class Solution {
public:
    int equalSubstring(string s, string t, int maxCost) {
        int n = s.size(), res = 0;
        vector<int> cost(n, 0);
        for (int i = 0; i < n; i++)
            cost[i] = abs(s[i] - t[i]);
        int l = 0, r = 0;
        for (int r = 0; r < n; r++) {
            maxCost -= cost[r];
            while (maxCost < 0)
                maxCost += cost[l++];
            res = max(res, r - l + 1);
        }
        return res;
    }
};

904. 水果成篮 1516

题意:fruits数组表示果树, 一排果树的种类。 采摘水果,要求:

  1. 只有两个篮子,每个篮子只能装一种水果,装的数量没有限制。
  2. 可以选任意一棵开始采摘,必须从每棵树恰好摘一个,应该符合篮子中水果类型。每摘一次向右移动一次。不符合就停止。 求最大水果数目
    分析: 模版题。
code
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        int res = 0, diffCnt = 0;
        unordered_map<int, int> cnt;
        for (int l = 0, r = 0; r < n; r++) {
            cnt[fruits[r]]++;
            if (cnt[fruits[r]] == 1)
                diffCnt++;
            while (diffCnt > 2) {
                cnt[fruits[l]]--;
                if (cnt[fruits[l]] == 0)
                    diffCnt--;
                l++;
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

1695. 删除子数组的最大得分 1529

题意:给一个整数数组nums,要删除一个含有 若干 不同元素 的子数组,删除的得分就是子数组元素和,返回只删一个子数组的最大得分
分析: 模版题

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

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

题意:给一个整数数组nums, 和一个整数k。
一个元素x 在数组中的频率指出现次数。求频率小于k的最长子数组。

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

2024. 考试的最大困扰度 1643

题意:老师出n道题, 每道答案 'T' 或者 'F' ,想增加不确定性,最大化连续相同结果的题数。 给一个字符串answerkey, 表示问题正确答案。还给一个k,每次可以将答案改成T 或者F, 返回不超过k次操作情况下, 最大连续T 或F数目。

分析: 一开始想的是T/F分情况两次滑窗,其实等价于 窗口内T/F都超过k 不行,其中一个不超过即可。 代码中判断TF的部分进一步可以根据ASCII码奇偶不同进一步优化。

code
class Solution {
public:
    int maxConsecutiveAnswers(string answerKey, int k) {
        int n = answerKey.size();
        int res = 0;
        vector<int> cnt(2, 0);
        for (int l = 0, r = 0; r < n; r++) {
            if (answerKey[r] == 'T')
                cnt[0]++;
            else
                cnt[1]++;
            while (cnt[0] > k && cnt[1] > k) {
                if (answerKey[l] == 'T')
                    cnt[0]--;
                else
                    cnt[1]--;
                l++;
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

1004. 最大连续 1 的个数 III 1656

题意: 给一个二进制数组nums 和一个整数k。假设最多可以反转k个0,返回执行操作后数组中连续1的最大个数。
分析: 找最长区间,里面0个数小于等于k

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

2.1.2 进阶

上面的基础做起来还是比较快的。感觉比定长的后面几题做起来舒服多了。

2730. 找到最长的半重复子字符串 非暴力做法

题意:给一个字符串s, 只包含0-9字符串。 如果一个字符串t中至多有一对相邻字符是相等的,称这个串t是半重复的。例如0010, 002020. 2002, 54944返回s中最长半重复的子字符串长度。

code
class Solution {
public:
    int longestSemiRepetitiveSubstring(string s) {
        int n = s.size(), res = 1;
        int same = 0;
        for (int l = 0, r = 0; r < n; r++) {
            if (r > 0 && s[r] == s[r - 1])
                same++;
            if (same > 1) {
                l++;
                while (s[l] != s[l - 1]) {
                    l++;
                }
                same = 1;
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

2779. 数组的最大美丽值 1638

题意:给一个下标从0开始的整数数组nums, 非负整数k。在一步操作中可以执行下述指令:

  1. 选一个之前没选过的下标i。
  2. 把nums[i]换成 nums[i]-k , nums[i] +k 的任一整数。
    数组美丽🈯值定义为数组中相等元素组成的最长子序列长度,

对数组执行上述操作任意次后,返回最大美丽值。
每个下标只能执行一次。

分析: 每个数字有一个变化区间, 求所有区间交集最多的那个数字是多少。哦哦先排序, 然后滑窗。

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

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

题意:给一个nums 和k , 每次可以移除最左边或最右边,然后从x减去。 如果可以将x恰好减到0, 返回最小操作数。
注意数据边界。

code
class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int n = nums.size(), res = -1, sum = 0;
        int total = reduce(nums.begin(), nums.end()); // total - x
        if (total - x < 0)
            return -1;
        for (int l = 0, r = 0; r < n; r++) {
            sum += nums[r];
            while (sum > total - x) {
                sum -= nums[l++];
            }
            if (sum + x == total)
                res = max(res, r - l + 1);
        }
        cout << res << endl;
        if (res == -1)
            return -1;
        return n - res;
    }
};

1838. 最高频元素的频数 1876

题意:元素的频数是元素在数组中出现次数,给一个nums 和一个k , 每次可以次选择nums一个下标,把对应元素值加1. 执行最多k次操作后,返回最高频元素的最大可能频数。
分析: 排序后维护一个窗口,窗口中元素都变成最右端的target 元素,

code
class Solution {
public:
    int maxFrequency(vector<int>& nums, int k) {
        int n = nums.size();
        sort(nums.begin(), nums.end());

        long long sum = 0;
        int  res = 0;
        for (int l = 0, r = 0; r < n; r++) {
            sum += nums[r];
            while (1LL* nums[r] * (r - l + 1) > sum + k) {
                sum -= nums[l++];
            }
            res = max(res, r - l + 1);
        }
        return res;
    }
};

2516. 每种字符至少取 K 个 1948

题意:给一个由字符 'a','b','c' 组成的字符串s 和一个非负整数k 。每分钟可以选择取走s最左侧还是最右侧的字符。 必须取走每种字符至少k个,返回需要的最少分钟数。
如果无法做到返回-1.

code
class Solution {
public:
    int takeCharacters(string s, int k) {
        int n = s.size();
        vector<int> cnt(3, 0);
        for (int i = 0; i < n; i++)
            cnt[s[i] - 'a']++;
        for (int i = 0; i < 3; i++) {
            if (cnt[i] < k)
                return -1;
            cnt[i] -= k;
        }
    
        int sum = 0, res = 0;
        for (int l = 0, r = 0; r < n; r++) {
            cnt[s[r] - 'a']--;
            while (cnt[s[r] - 'a'] < 0) {
                cnt[s[l++] - 'a']++;
            }
            res = max(res, r - l + 1);
        }
        return n - res;
    }
};

2831. 找出最长等值子数组 1976

题意:给一个nums 和整数 k ,如果所有元素相等,认为是一个等值子数组。 空数组是等值子数组。从nums 中删除最多k个元素后, 返回可能的最长等值子数组的长度。
分析:想了一会没见过这类题,看了灵神题解妙啊。 分组+滑窗。 尤其是 优化了 pos 数组存储内容 简化最终判断条件比较优雅。

code
class Solution {
public:
    int longestEqualSubarray(vector<int>& nums, int k) {
        int n = nums.size();
        int res = 0;
        vector<vector<int>> pos_list(n + 1);
        for (int i = 0; i < n; i++) {
            int x = nums[i];
            pos_list[x].push_back(i - pos_list[x].size());
        }
        for (auto& pos : pos_list) {
            int l = 0;
            for (int r = 0; r < pos.size(); r++) {
                while (pos[r] - pos[l] > k) {
                    l++;
                }
                res = max(res, r - l + 1);
            }
        }

        return res;
    }
};

2271. 毯子覆盖的最多白色砖块数 2022

题意:给一个二维数组tiles,其中 tiles[l_i, r_i]表示在l_i, r_i 之间的每个瓷砖位置j 都涂成了白色。
同时给一个整数 carpetLen , 表示可以放在任何位置的一块毯子的长度,返回使用这块毯子,最多可以盖住多少块白色瓷砖。

/

code
class Solution {
public:
    int maximumWhiteTiles(vector<vector<int>>& tiles, int carpetLen) {
        int n = tiles.size();
        int res = 0;
        ranges::sort(tiles, {}, [](auto& t) { return t[0]; });
        int cover = 0, l = 0;
        for (auto& tile : tiles) {
            int tl = tile[0], tr = tile[1];
            cover += tr - tl + 1;
            int carpetLeft = tr - carpetLen + 1;
            while (tiles[l][1] < carpetLeft) {
                cover -= (tiles[l][1] - tiles[l][0] + 1);
                l++;
            }
            int uncover = max(0, carpetLeft - tiles[l][0]);
            res = max(res, cover - uncover);
        }

        return res;
    }
};

2106. 摘水果 2062

题意:x坐标轴上有水果, 二维数组 fruits , fruits[i]= [position_i, amount_i], 表示posi上一共有 amount_i个水果。fruits已经按position升序。每个position 互不相同。
另外给两个整数 startPos 和 k, 最初位于startPos。 可以向左/向右,最多走k步,返回摘到最大水果数。

好像可以把人的范围确定出来。 l = startPos-k+1, r = startPos+k. 但是如果要两个方向都摘,那么需要最短的距离*2+ 长的距离。
不妨从l,r 维护所有水果是否要摘,每次向右摘不到的就放弃最左边的。 统计最大值即可。

/

code
class Solution {
public:
    int maxTotalFruits(vector<vector<int>>& fruits, int startPos, int k) {
        int n = fruits.size();
        int min_l = max(0, startPos - k), max_r =  startPos + k;
        int l = 0, r = 0;
        int res = 0, cur = 0;
        while (l < n && fruits[l][0] < min_l)
            l++;
        r = l;

        while (r < n && fruits[r][0] <= startPos) {
            cur += fruits[r][1];
            r++;
        }
        res = cur;
        for (; r < n && fruits[r][0] <= max_r; r++) {
            cur += fruits[r][1];
            while (fruits[r][0] * 2 - startPos - fruits[l][0] > k &&
                   fruits[r][0] + startPos - fruits[l][0] * 2 > k) {
                cur -= fruits[l][1];
                l++;
            }
            res = max(res, cur);
        }

        return res;
    }
};

2555. 两个线段获得的最多奖品 2081

题意:x轴上奖品,给一个整数数组,prizePositions, 按照非递减顺序排列,其中prizePositions[i]是滴i件奖品的位置。数轴上一个位置可能有多件奖品,再给一个整数k。 可以同时选择两个端点为整数的线段,每个线段长度必须为k。可以获得线段上的奖品。返回在选择两个最优线段前提下,获得的最多的奖品数量。

  1. 线段尽量不相交。
  2. 确定了右端点后左端点就确定了。
  3. prizePositions 的范围为1e9
  4. k 的范围是1e9
  5. prizePositions长度为1e5
  6. 两个线段相交肯定不是最优解,那么就等价于算两次最优线段吧。

/

code

2.2 越长越合法/求最短/最小

209. 长度最小的子数组

题意:给一个n个正整数的数组和一个正整数 target。找出满足总和大于等于target 的长度最小的
子数组 [nums_l, nums_l+1, ... , nums[r] ],并返回其长度。 如果不存在符合条件子数组,返回0 。

code
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int res = n + 1, sum = 0;
        int l = 0, r = 0;
        while (r < n) {
            sum += nums[r];
            while (sum - nums[l] >= target) {
                sum -= nums[l++];
            }
            if (sum >= target) {
                res = min(res, r - l + 1);
            }
            r++;
        }
        return res <= n ? res : 0;
    }
};

3795. 不同元素和至少为 K 的最短子数组长度

题意:给一个整数数组nums和一个整数k
返回一个子数组的最小长度, 使得子数组中出现不同值之和至少为k。 如果不存在这样的子数组,返回 -1。

/

code
class Solution {
public:
    int minLength(vector<int>& nums, int k) {
        int n = nums.size();
        int res = INT_MAX, sum = 0;
        int l = 0, r = 0;
        unordered_map<int, int> mp;
        while (r < n) {
            if (!mp[nums[r]]) {
                sum += nums[r];
            }
            mp[nums[r]]++;
            while (sum >= k) {
                res = min(res, r - l + 1);
                mp[nums[l]]--;
                if (mp[nums[l]] == 0)
                    sum -= nums[l];
                l++;
            }

            r++;
        }
        return res < INT_MAX ? res : -1;
    }
};

2904. 最短且字典序最小的美丽子字符串

题意:给一个二进制字符串s 和一个整数k
如果s的某个子字符串中1的个数恰好等于k, 那么称这字符串是一个美丽字符串。
令 len 等于最短美丽子字符串的长度,返回长度等于len且字典序最小的美丽子字符串。 如果s 中不含美丽字符串,返回一个空串。

code
class Solution {
public:
    string shortestBeautifulSubstring(string s, int k) {
        int n = s.size();
        string res = "";
        if (ranges::count(s, '1') < k) {
            return "";
        }
        res = s;
        int l = 0, r = 0, cnt1 = 0;
        while (r < n) {
            if (s[r] == '1')
                cnt1++;
            while (cnt1 > k || s[l] == '0') {
                cnt1 -= s[l++] - '0';
                
            }
            if (cnt1 == k) {
                string t = s.substr(l, r - l + 1);
                if (t.length() < res.length() ||
                    t.length() == res.length() && t < res) {
                    res = move(t);
                }
            }
            r++;
        }
        return res;
    }
};

1234. 替换子串得到平衡字符串

题意: 一个只包含 Q W E R 的 长度为n 的字符串。长度为n
假如在该字符串中,四个字符 都恰好出现n/4次,那么是一个平衡字符串。
给一个字符串s, 通过替换一个子串方式,使得原来的s 变成一个平衡的, 可以用长度相同的任何串来替换。
返回替换子串的最小可能长度。
如果本身就是平衡的,返回0.

code
class Solution {
public:
    int balancedString(string s) {
        int n = s.size();
        int res = n;
        unordered_map<char, int> mp;
        for (int i = 0; i < n; i++)
            mp[s[i]]++;
        if (mp['Q'] == n / 4 && mp['W'] == n / 4 && mp['E'] == n / 4 &&
            mp['R'] == n / 4)
            return 0;

        int l = 0, r = 0;
        while (r < n) {
            mp[s[r]]--;
            while (mp['Q'] <= n / 4 && mp['W'] <= n / 4 && mp['E'] <= n / 4 &&
                   mp['R'] <= n / 4) {
                res = min(res, r - l + 1);
                mp[s[l]]++;
                l++;
            }
            r++;
        }
        return res;
    }
};

2875. 无限数组的最短子数组 1914

题意:给一个下标从 0 开始的数组 nums 和一个整数target
infinite_nums 是通过无限的将nums元素追加到自己之后生成的
从 infinite_nums 中找出满足元素和等于 target 的最短子数组,返回该数组长度,不存在返回-1

code
class Solution {
public:
    int minSizeSubarray(vector<int>& nums, int target) {
        int n = nums.size();
        long long total = reduce(nums.begin(), nums.end(), 0LL);
        int res = INT_MAX;
        long long sum = 0;
        int l = 0;
        for (int r = 0; r < n * 2; r++) {
            sum += nums[r % n];
            while (sum > target % total) {
                sum -= nums[l % n];
                l++;
            }
            if (sum == target % total) {
                res = min(res, r - l + 1);
            }
        }

        return res == INT_MAX ? -1 : res + target / total * n;
    }
};

76. 最小覆盖子串

题意: 给两个字符串 s 和 t, 长度分别是 m 和 n,返回s中的最短窗口的子串,使得该子串包含t 中的每一个字符(包括重复字符)
如果没有这样的子串,返回空字符串。

code
class Solution {
public:
    string minWindow(string s, string t) {
        int lens = s.size(), lent = t.size();
        unordered_map<char, int> cnt;
        int diff = 0, res = s.size() + 1;
        for (int i = 0; i < lent; i++) {
            if (cnt[t[i]] == 0)
                diff++;
            cnt[t[i]]++;
        }
        int l = 0;
        int res_l = -1, res_r = lens;
        for (int r = 0; r < lens; r++) {
            cnt[s[r]]--;
            if (cnt[s[r]] == 0)
                diff--;

            while (diff == 0) {
                if (r - l < res_r - res_l) {
                    res_l = l;
                    res_r = r;
                }
                cnt[s[l]]++;
                if (cnt[s[l]] == 1)
                    diff++;

                l++;
            }
        }
        return res_l < 0 ? "" : s.substr(res_l, res_r - res_l + 1);
    }
};

632. 最小区间 (TODO)

题意:给一个 k 个非递减的整数列表, 找到一个最小区间 ,使得 k 个列表中的每个列表至少有一个数包含在其中。
// ps 这题先放放。没看懂。

2.3 求子数组个数

2.3.1 越短越合法

一般要写 res += r - l + 1
内层循环结束后 , [l,r] 这个子数组满足题目要求。由于子数组越短,越能满足要求,所以除了[l,r], 还有[l+1,r]... [r,r]。

713. 乘积小于 K 的子数组

题意:给一个整数数组nums 和一个整数k, 返回子数组中所有元素乘积 严格小于 k 的连续子数组数目;。

code
class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k <= 1)
            return 0;
        int n = nums.size();
        long long mul = 1;
        int res = 0;
        int l = 0;
        for (int r = 0; r < n; r++) {
            mul *= nums[r];
            while (mul >= k) {
                mul /= nums[l];
                l++;
            }
            res += r - l + 1;
        }
        return res;
    }
};

3258. 统计满足 K 约束的子字符串数量 I 做到 O(n)

题意: 给一个二进制字符串s 和一个整数k
如果一个二进制字符串满足以下任意一个条件,那么认为该字符串满足k 约束: 1. 字符串中0的数量最多为k 2. 字符串中1的数量最多为k。
返回一个整数,表示s 中所有满足k 约束的子字符串数量。

code
class Solution {
public:
    int countKConstraintSubstrings(string s, int k) {
        int n = s.size();
        int res = 0;
        int l = 0, cnt0 = 0, cnt1 = 0;
        for (int r = 0; r < n; r++) {
            s[r] == '0' ? cnt0++ : cnt1++;
            while (cnt0 > k && cnt1 > k) {
                s[l] == '0' ? cnt0-- : cnt1--;
                l++;
            }
            res += r - l + 1;
        }

        return res;
    }
};

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

题意:一个数组的分数定义为数组之和乘以数组的长度。
给一个正整数 nums 和一个整数 k。 返回nums中分数严格小于 k 的非空整数子数组数目。

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

2762. 不间断子数组 1940

题意:给一个下标从 0 开始的整数数组nums, 子数组满足以下条件,那么他是不间断的。
i , i+1, ..., j表示下标。 对于满足 i<=i_1, i_2 <= j 的下标对,都有 0 <= |nums[i_1] - nums[i_2]| <=2 .
返回不间断子数组的总数目。

code
class Solution {
public:
    long long continuousSubarrays(vector<int>& nums) {
        long long res = 0;
        int n = nums.size();
        int l = 0;
        map<int, int> cnt;
        for (int r = 0; r < n; r++) {
            cnt[nums[r]]++;
            while (cnt.rbegin()->first - cnt.begin()->first > 2) {
                cnt[nums[l]]--;
                if (cnt[nums[l]] == 0)
                    cnt.erase(nums[l]);
                l++;
            }
            res += r - l + 1;
        }
        return res;
    }
};

LCP 68. 美观的花束

题意:一维数组 flowers 每个数字表示鲜花品种。可以选择一段区间鲜花做成插花,不能丢弃。在选择的区间,每一种花的数量不超过cnt,
那么认为是美观的。返回总共多少种可能的区间。

code
class Solution {
public:
    int beautifulBouquet(vector<int>& flowers, int cnt2) {
        int n = flowers.size();
        int res = 0;
        int l = 0;
        unordered_map<int, int> cnt;
        for (int r = 0; r < n; r++) {
            cnt[flowers[r]]++;
            while (cnt[flowers[r]] > cnt2) {
                cnt[flowers[l]]--;
                l++;
            }
            res += r - l + 1;
        }
        return res;
    }
};

§2.3.2 越长越合法

一般会写 res += l

1358. 包含所有三种字符的子字符串数目 1646

题意:字符串s , 只包含三种字符a,b,c。 返回a,b,c 都至少出现过一次的子字符串的数目。

code
class Solution {
public:
    int numberOfSubstrings(string s) {
        int n = s.size();
        int l = 0, res = 0;
        int cnt[3] = {0, 0, 0};
        for (int r = 0; r < n; r++) {
            cnt[s[r] - 'a']++;
            while (cnt[0] && cnt[1] && cnt[2]) {
                cnt[s[l] - 'a']--;
                l++;
            }
            res += l;
        }
        return res;
    }
};

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

给一个整数数组nums 和一个正整数 k。
统计多少满足nums 中的最大元素至少出现k次的子数组, 并返回满足这一条件的子数组数目。

note : 注意题意 max 是整个nums数组的, 不是子数组的。

code
class Solution {
public:
    long long countSubarrays(vector<int>& nums, int k) {
        int n = nums.size();
        int mx = ranges::max(nums);
        map<int, int> cnt;
        int l = 0,  mxcnt = 0;
        long long res = 0;
        for (int r = 0; r < n; r++) {
            mxcnt += nums[r] == mx ? 1 : 0;
            while (mxcnt >= k) {
                mxcnt -= mx == nums[l] ? 1 : 0;
                l++;
            }
            res += l;
        }
        return res;
    }
};

3325. 字符至少出现 K 次的子字符串 I 做到 O(n)

题意: 给一个字符串s 和一个整数k ,在s 的所有子字符串中,统计并返回至少有一个
字符至少出现k 次的子字符串总数。

code
class Solution {
public:
    int numberOfSubstrings(string s, int k) {
        int n = s.size();
        int res = 0;
        unordered_map<char, int> cnt;
        int l = 0;
        for (int r = 0; r < n; r++) {
            cnt[s[r]]++;
            while (cnt[s[r]] >= k) {
                cnt[s[l]]--;
                l++;
            }
            res += l;
        }
        return res;
    }
};

2062. 统计字符串中的元音子字符串 做到 O(n)

题意:元音子字符串是仅由元音组成的子字符串,且必须包含全部5种元音
给一个字符串word, 统计并返回word 中元音子字符串的数目

注意: 遇到非元音 需要清空cnt。

code
class Solution {
public:
    bool isvol(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
    int countVowelSubstrings(string word) {
        int n = word.size();
        int lastv = 0;
        int l = 0;
        unordered_map<char, int> cnt;
        int res = 0;
        for (int r = 0; r < n; r++) {
            if (isvol(word[r])) {
                cnt[word[r]]++;
                while (cnt['a'] && cnt['e'] && cnt['i'] && cnt['o'] &&
                       cnt['u']) {
                    cnt[word[l]]--;
                    l++;
                }
                res += l - lastv;
            } else {
                cnt.clear();
                lastv = r + 1;
                l = r + 1;
            }
        }
        return res;
    }
};

2799. 统计完全子数组的数目 做到 O(n)

题意: 一个正整数数组nums, 子数组满足以下条件,称为完全子数组:
子数组中不同元素数目,等于整个数组不同元素数目。
返回子数组中完全子数组数目。
ps: 熟练运用unordered_set, unordered_map 对代码简洁很有帮助。

code
class Solution {
public:
    int countCompleteSubarrays(vector<int>& nums) {
        int n = nums.size();
        int res = 0, l = 0;
        unordered_set<int> st(nums.begin(), nums.end());
        unordered_map<int, int> cnt;
        int diff = st.size();
        for (int r = 0; r < n; r++) {
            cnt[nums[r]]++;
            while (cnt.size() == diff) {
                cnt[nums[l]]--;
                if (cnt[nums[l]] == 0) {
                    cnt.erase(nums[l]);
                }
                l++;
            }
            res += l;
        }
        return res;
    }
};

2537. 统计好子数组的数目 1892

题意: 给一个整数数组nums 和一个k , 返回nums中好子数组的数目。
arr 至少 k 对下标 (i,j) 满足 i < j 且 arr[i] == arr[j] , 那么他是一个好子数组。

code
class Solution {
public:
    long long countGood(vector<int>& nums, int k) {
        long long res = 0;
        int n = nums.size();
        unordered_map<int, long long> cnt;        
        int l = 0;        
        long long pairs= 0 ;
        for(int r = 0; r < n ;r++){
            pairs += cnt[nums[r]]; 
            cnt[nums[r]]++;            
            
            while(pairs >= k){
                cnt[nums[l]]--;
                pairs -= cnt[nums[l]];
                l++;
            }            
            res += l;
        }
        return res;
    }
};

3298. 统计重新排列后包含另一个字符串的子字符串数目 II 1909 同 76 题

题意:给两个字符串 word1 和 word2
如果一个字符串 x 重新排列后, word2 是重排的前缀, 那么称 x 是合法的。
返回word1 中合法子字符串的数量。

code
class Solution {
public:
    long long validSubstringCount(string word1, string word2) {
        long long res = 0;
        int len1 = word1.size(), len2 = word2.size();
        int diff = 0;
        unordered_map<int, int> cnt;
        for (auto x : word2) {
            if (!cnt[x])
                diff++;
            cnt[x]++;
        }

        int l = 0;
        for (int r = 0; r < len1; r++) {
            cnt[word1[r]]--;
            if (cnt[word1[r]] == 0) {
                diff--;
            }
            while (diff == 0) {
                cnt[word1[l]]++;
                if (cnt[word1[l]] == 1)
                    diff++;
                l++;
            }
            res += l;
        }

        return res;
    }
};

恰好型滑动窗口

930. 和相同的二元子数组 1592

题意: 给一个二元数组nums, 和一个整数 goal。统计并返回有多少个和为goal 的非空子数组。

和 >= goal 的 减去 和 > goal 的 就是恰好等于goal 的。

code
class Solution {
public:
    int numSubarraysWithSum(vector<int>& nums, int goal) {
        int n = nums.size();
        int res = 0;
        int l1 = 0, l2 = 0;
        int sum1 = 0, sum2 = 0;
        for (int r = 0; r < n; r++) {
            sum1 += nums[r];
            while (l1 <= r && sum1 >= goal) {
                sum1 -= nums[l1];
                l1++;
            }
            res += l1;

            sum2 += nums[r];
            while (sum2 > goal) {
                sum2 -= nums[l2];
                l2++;
            }
            res -= l2;
        }
        return res;
    }
};

1248. 统计「优美子数组」 1624

题意:给一个整数数组nums,和一个整数k, 如果某个连续子数组中恰好有k 个奇数数字,我们认为是优美子数组。
返回数组中优美子数组个数。
ps: 这类题在>=时通常要判断l <=r ,因为窗口缩到0时可能还是满足,可能会越界,但是 > 条件一般不用。

code
class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        int n = nums.size(), res = 0, l1 = 0, l2 = 0, sum1 = 0, sum2 = 0;
        for (int r = 0; r < n; r++) {
            sum1 += nums[r] % 2;
            while (l1 <= r && sum1 >= k) {
                sum1 -= nums[l1++] % 2;
            }
            res += l1;
            sum2 += nums[r] % 2;
            while (sum2 > k) {
                sum2 -= nums[l2++] % 2;
            }
            res -= l2;
        }
        return res;
    }
};

3306. 元音辅音字符串计数 II 2200

题意:给一个字符串word 和一个非负整数 k。
返回word 的子字符串中, 每个元音字母至少出现一次,且恰好包含k个辅音字符的子字符串总数。

code
class Solution {
public:
    bool isVol(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
    long long f(string& word, int k) {
        long long res = 0;
        unordered_map<char, int> cnt;
        int n = word.size(), l = 0;
        int sum = 0;
        for (int r = 0; r < n; r++) {
            bool isvol = isVol(word[r]);
            if (isvol)
                cnt[word[r]]++;
            else
                sum++;
            while (cnt.size() == 5 && sum >= k) {
                char out = word[l];
                if (isVol(out)) {
                    if (--cnt[out] == 0) {
                        cnt.erase(out);
                    }
                } else {
                    sum--;
                }
                l++;
            }
            res += l;
        }
        return res;
    }
    long long countOfSubstrings(string word, int k) {
        return f(word, k) - f(word, k + 1);
    }
};

992. K 个不同整数的子数组 2210

题意:给一个正整数数组 nums 和一个整数k 。返回nums 中好子数组的数目
如果nums 的某个子数组中不同整数 的个数恰好为k ,则称nums这个连续、不一定不同的子数组为好子数组。

code
class Solution {
public:
    int f(vector<int>& nums, int k) {
        int n = nums.size(), res = 0, l = 0;
        unordered_map<int, int> cnt;
        int diff = 0;
        for (int r = 0; r < n; r++) {
            cnt[nums[r]]++;
            if (cnt[nums[r]] == 1)
                diff++;
            while (diff >= k) {
                cnt[nums[l]]--;
                if (cnt[nums[l]] == 0)
                    diff--;
                l++;
            }
            res += l;
        }
        return res;
    }
    int subarraysWithKDistinct(vector<int>& nums, int k) {
        return f(nums, k) - f(nums, k + 1);
    }
};

3859. 统计包含 K 个不同整数的子数组 2302

题意:给一个整数数组nums 和两个整数 k 和 m
返回一个整数,表示满足以下条件的子数组的数量。

  1. 子数组恰好包含 k 个不同整数
  2. 在子数组中, 每个不同的整数至少出现 m 次
code
class Solution {
public:
    long long f(vector<int>& nums, int k, int limit, int m) {
        long long res = 0;
        unordered_map<int, int> cnt;
        int n = nums.size(), l = 0, ge_m = 0;
        for (int r = 0; r < n; r++) {
            cnt[nums[r]]++;
            if (cnt[nums[r]] == m)
                ge_m++;
            while (cnt.size() >= k && ge_m >= limit) {
                int out = nums[l];
                if (cnt[out] == m) {
                    ge_m--;
                }
                if (--cnt[out] == 0) {
                    cnt.erase(out);
                }
                l++;
            }
            res += l;
        }
        return res;
    }
    long long countSubarrays(vector<int>& nums, int k, int m) {
        return f(nums, k, k, m) - f(nums, k + 1, k, m);
    }
};
posted on 2026-05-31 17:46  Eura42  阅读(13)  评论(0)    收藏  举报