灵神的LC题单
2025.4.7 今天开始刷灵神的LC题单
1. 定长滑动窗口
所有定长滑动窗口的题目都可以用如下模板解决:
int slow = 0, int fast = 0;
int state; // 记录状态
int ans; // 结果
while (fast < n) {
// 1. 窗口右侧插入元素,判断其对状态的改变
if (fast++ < k - 1)
continue;
// 2. 更新结果
// 3. 窗口左侧删除元素,判断其对状态的改变
}
1456. 定长子串中元音的最大数目
https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/description/
-
最简单的定长滑动窗口题
- 向右滑动时判断移入字符和移除字符是否为元音,以此修改cnt的值
class Solution { public: bool isVow(char ch) { if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') return true; return false; } int maxVowels(string s, int k) { int cnt = 0, ans = 0; for(int i = 0; i < s.length(); i++) { if (isVow(s[i])) cnt++; if (i < k - 1) // fast == k-1时,即fast指向第k个元素时继续往下执行 continue; ans = max(ans, cnt); if (isVow(s[i-k+1])) cnt--; } return ans; } };- 时间复杂度:O(n),n为字符串s的长度
- 空间复杂度:O(1)
643. 子数组最大平均数 I
https://leetcode.cn/problems/maximum-average-subarray-i/description/
-
定长滑动窗口题
- 直接上模板
class Solution { public: double findMaxAverage(vector<int>& nums, int k) { int sum = 0; int slow = 0, fast = 0; int ans = INT_MIN; while (fast < nums.size()) { sum += nums[fast]; // insert滑动窗口右侧 if (fast++ < k - 1) continue; ans = max(ans, sum); // 更新结果 sum -= nums[slow++]; // delete滑动窗口左侧 } return (double)ans / k; } };- 时间复杂度:O(n),n为nums数组的长度
- 空间复杂度:O(1)
1343. 大小为 K 且平均值大于等于阈值的子数组数目
-
定长滑动窗口
class Solution { public: int numOfSubarrays(vector<int>& arr, int k, int threshold) { int slow = 0, fast = 0; int ans = 0, sum = 0; while (fast < arr.size()) { sum += arr[fast]; if (fast++ < k - 1) continue; if (sum >= threshold * k) ans++; sum -= arr[slow++]; } return ans; } };- 时间复杂度:O(n),n为arr数组的长度
- 空间复杂度:O(1)
2090. 半径为 k 的子数组平均值
https://leetcode.cn/problems/k-radius-subarray-averages/description/
-
定长滑动窗口
- 这道题目复杂的地方在于将半径范围转换为模板中的窗口形式
class Solution { public: vector<int> getAverages(vector<int>& nums, int k) { int n = nums.size(); vector<int> ans(n, -1); // 预留空间并全部初始化为-1,避免扩容 long long sum = 0; // 必须long long,否则会溢出 for (int i = 0; i < n; i++) { sum += nums[i]; if (i < 2 * k) continue; ans[i-k] = sum / (2 * k + 1); sum -= nums[i - 2*k]; } return ans; } };- 时间复杂度:O(n),n为nums数组的长度
- 空间复杂度:O(1)
2379. 得到 K 个黑块的最少涂色次数
https://leetcode.cn/problems/minimum-recolors-to-get-k-consecutive-black-blocks/description/
-
定长滑动窗口
- 题目要求得到K个黑块最少涂色次数,所谓涂色次数其实就是滑动窗口中白块的数量。
- 最少涂色次数就是滑动窗口中所遇到的最少白块数量。
class Solution { public: int minimumRecolors(string blocks, int k) { int ans = INT_MAX, cnt = 0; for (int i = 0; i < blocks.length(); i++) { if (blocks[i] == 'W') cnt++; if (i < k - 1) continue; ans = min(ans, cnt); if (blocks[i - (k-1)] == 'W') cnt--; } return ans; } };- 时间复杂度:O(n),n为blocks数组的长度
- 空间复杂度:O(1)
2841. 几乎唯一子数组的最大和
https://leetcode.cn/problems/maximum-sum-of-almost-unique-subarray/description/
-
定长滑动窗口
- 需要快速对窗口中存在的不同元素个数进行统计 -> 哈希表,注意哈希表元素删除的条件
class Solution { public: long long maxSum(vector<int>& nums, int m, int k) { // unordered_map来保存滑动窗口中互不相同的元素 // 因此判断窗口中是否有m个互不相同的元素,即判断unordered_map的size是否大于等于m unordered_map<int, int> mp; long long ans = 0, sum = 0; for (int i = 0; i < nums.size(); i++) { sum += nums[i]; mp[nums[i]]++; if (i < k - 1) continue; if (mp.size() >= m) ans = max(ans, sum); int t = nums[i - (k-1)]; sum -= t; if(--mp[t] == 0) mp.erase(t); } return ans; } };- 时间复杂度:O(n),n为nums数组的长度
- 空间复杂度:O(k),哈希表的长度不会超过滑动窗口长度k
2461. 长度为 K 子数组中的最大和
https://leetcode.cn/problems/maximum-sum-of-distinct-subarrays-with-length-k/description/
-
定长滑动窗口
- 同上道题,只是不重复元素变为滑动窗口大小
class Solution { public: long long maximumSubarraySum(vector<int>& nums, int k) { // unordered_map来保存滑动窗口中互不相同的元素 // 因此判断窗口中是否有m个互不相同的元素,即判断unordered_map的size是否大于等于m unordered_map<int, int> mp; long long ans = 0, sum = 0; for (int i = 0; i < nums.size(); i++) { sum += nums[i]; mp[nums[i]]++; if (i < k - 1) continue; if (mp.size() == k) ans = max(ans, sum); int t = nums[i - (k-1)]; sum -= t; if(--mp[t] == 0) mp.erase(t); } return ans; } };- 时间复杂度:O(n),n为nums数组的长度
- 空间复杂度:O(k),哈希表的长度不会超过滑动窗口长度k
1423. 可获得的最大点数
https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/description/
-
定长滑动窗口
- 这道题的不同之处在于它隐藏得很深,第一眼看过去,很难直接想到是滑动窗口的题目
- 但实际上,仍然是定长滑动窗口题,只不过要以循环数组的视角去看
class Solution { public: int maxScore(vector<int>& cardPoints, int k) { int ans = 0, sum = 0; int n = cardPoints.size(); int left = n - k, right = n - k; int realRight = right; // 记录真正的窗口右端点位置 for (int i = 0; i < 2 * k; i++, realRight = (realRight + 1) % n) { sum += cardPoints[realRight]; if (right++ < n - 1) continue; ans = max(ans, sum); sum -= cardPoints[left]; left = (left + 1) % n; } return ans; } };- 时间复杂度:O(k)
- 空间复杂度:O(1)
1052. 爱生气的书店老板
https://leetcode.cn/problems/grumpy-bookstore-owner/description/
-
定长滑动窗口
- 需要将问题转换一下
class Solution { public: int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) { // 滑动窗口题,将问题转换一下,就是求大小为minuts的窗口内最多不满意顾客的数量 int total = 0, unstCnt = 0; for (int i = 0; i < customers.size(); i++) { // 求顾客总数和不满意顾客数量 total += customers[i]; if (grumpy[i]) unstCnt += customers[i]; } int ans = 0, cnt = 0; // cnt记录滑动窗口内不满意顾客的数量 for (int i = 0; i < customers.size(); i++) { if (grumpy[i]) cnt += customers[i]; if (i < minutes - 1) continue; ans = max(ans, cnt); if (grumpy[i-minutes+1]) cnt -= customers[i-minutes+1]; } return total - unstCnt + ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
1652. 拆炸弹
https://leetcode.cn/problems/defuse-the-bomb/description/
-
定长滑动窗口
- 分三种不同情况,注意k<0时,记得将k转换为绝对值
class Solution { public: vector<int> decrypt(vector<int>& code, int k) { int n = code.size(); vector<int> ans(n, 0); if (k == 0) { // k == 0的情况 return ans; } else if (k > 0) { // k > 0的情况 int sum = 0; for (int i = 0; i < k; i++) sum += code[i]; for (int j = 0, left = n - 1, right = k - 1; j < n; j++) { ans[left] = sum; sum += code[left]; sum -= code[right]; left--; right = (right + n - 1) % n; } } else { // k < 0的情况 int sum = 0; k = -k; for (int i = n-k; i < n; i++) sum += code[i]; for (int j = 0, left = n - k, right = 0; j < n; j++) { ans[right] = sum; sum += code[right]; sum -= code[left]; right++; left = (left + 1) % n; } } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
今天就刷到这里,学别的去了~
2025.4.8 早上9点17分,开始新一天的刷题,干!
3439. 重新安排会议得到最多空余时间 I
https://leetcode.cn/problems/reschedule-meetings-for-maximum-free-time-i/description/
-
定长滑动窗口
- 我一开始自己做这道题没想出来,看了灵神的题解才弄明白
- 这道题目的难点在于如何将题目的要求转换为使用滑动窗口能解决的形式
- 题目告诉我们可以移动k个会议,求移动后最长连续空余时间,这可以转换为从n+1个空余时间块中求出k+1个空余时间块之和的最大值
- 我们可以通过startTime和endTime两个数组,进行简单的变换,从而抽象出一个空余时间数组
- 然后在该抽象的空余时间数组上使用滑动窗口
class Solution { public: int maxFreeTime(int eventTime, int k, vector<int>& startTime, vector<int>& endTime) { int n = startTime.size(); auto get = [&](int i) { // 通过这个get函数,抽象出一个空闲时间数组 if (i == 0) { return startTime[0]; } if (i == n) { return eventTime - endTime[n-1]; } return startTime[i] - endTime[i-1]; }; int sum = 0, ans = 0; for (int i = 0; i <= n; i++) { // 滑动窗口 sum += get(i); if (i < k) continue; ans = max(ans, sum); sum -= get(i - k); } return ans; } };- 时间复杂度:O(n),n为startTime数组的长度
- 空间复杂度:O(1)
2134. 最少交换次数来组合所有的 1 II
https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together-ii/description/
-
定长滑动窗口
- 这道题目需要自己确定滑动窗口的大小和统计目标
- 因为目的是将所有1聚集在一起,因此滑动窗口的大小为数组中1的总数,统计目标为窗口内0的最小值,表示要将所有1聚集在一起需要交换的最少次数
class Solution { public: int minSwaps(vector<int>& nums) { int cntOnes = 0; for (auto x : nums) cntOnes += (x == 1); if (cntOnes == 0) return 0; // 数组中元素全为0 int n = nums.size(); int cntZeros = 0, ans = INT_MAX; for (int i = 0, left = 0, right = 0; i < n + cntOnes - 1; i++, right = (right+1) % n) { cntZeros += (nums[right] == 0); if (i < cntOnes - 1) continue; ans = min(ans, cntZeros); if (ans == 0) return 0; // 滑动窗口中全为1,可直接退出 cntZeros -= (nums[left++] == 0); } return ans; } };- 时间复杂度:O(n),n为nums数组的长度
- 空间复杂度:O(1)
上午就做到这了,1297.子串最大出现次数没有做出来,滑动窗口就先这样,后面再回来把题目刷完。
2. 不定长滑动窗口
下面这些题的目标都是最长/最大。
3. 无重复字符的最长子串
https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/
-
哈希表+不定长滑动窗口
- 不定长滑动窗口的分为三步:插入右端点、while循环移动左端点保持条件的满足、更新结果
class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map<char, int> mp; int ans = 0, left = 0, n = s.length(); for (int right = 0; right < n; right++) { char c = s[right]; // 插入 mp[c]++; while (mp[c] > 1) { // 通过移动左端保持状态的满足 mp[s[left++]]--; } ans = max(ans, right - left + 1); // 更新结果 } return ans; } };-
时间复杂度:O(n),n为字符串s的长度
-
空间复杂度:O(n)
3090. 每个字符最多出现两次的最长子字符串
https://leetcode.cn/problems/maximum-length-substring-with-two-occurrences/description/
-
同上题,条件变了下
class Solution { public: int maximumLengthSubstring(string s) { int ans = 0, left = 0, n = s.length(); unordered_map<char, int> mp; for (int right = 0; right < n; right++) { char c = s[right]; mp[c]++; while (mp[c] > 2) { mp[s[left++]]--; } ans = max(ans, right - left + 1); } return ans; } }; -
用数组作为哈希表:
- 这道题规定s仅由小写英文字母组成
class Solution { public: int maximumLengthSubstring(string s) { int ans = 0, left = 0, n = s.length(); int mp[26]{}; for (int right = 0; right < n; right++) { char c = s[right]; mp[c - 'a']++; while (mp[c - 'a'] > 2) { mp[s[left++] - 'a']--; } ans = max(ans, right - left + 1); } return ans; } };-
时间复杂度:O(n),n为字符串s的长度
-
空间复杂度:O(1)
2025.4.10 继续做变长滑动窗口题
1493. 删掉一个元素以后全为 1 的最长子数组
https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/description/
-
不定长滑动窗口
- 求删除掉一个元素后全为1的最大窗口长度
- 转换成白话就是窗口内含0的个数不超过1的最大窗口长度
class Solution { public: int longestSubarray(vector<int>& nums) { // 变长滑动窗口 // 统计删掉一个元素后全为1的最大窗口长度 int cnt = 0, ans = 0; for (int left = 0, right = 0; right < nums.size(); right++) { if (nums[right] == 0) cnt++; while (cnt > 1) { if (nums[left++] == 0) cnt--; } ans = max(ans, right - left + 1); } return ans - 1; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
1208. 尽可能使字符串相等
https://leetcode.cn/problems/get-equal-substrings-within-budget/description/
-
变长滑动窗口
- 标准的变长滑动窗口题,没啥好说的
class Solution { public: int equalSubstring(string s, string t, int maxCost) { int ans = 0; for (int left = 0, right = 0; right < s.length(); right++) { maxCost -= abs(s[right] - t[right]); while (maxCost < 0) { maxCost += abs(s[left] - t[left]); left++; } ans = max(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
904. 水果成篮
https://leetcode.cn/problems/fruit-into-baskets/description/
-
变长滑动窗口
- 不讲人话的题目,就是求不同元素不超过2个的最大子数组长度
class Solution { public: int totalFruit(vector<int>& fruits) { unordered_map<int, int> mp; int ans = 0; for (int left = 0, right = 0; right < fruits.size(); right++) { mp[fruits[right]]++; while (mp.size() > 2) { int t = fruits[left]; mp[t]--; if (mp[t] == 0) mp.erase(t); left++; } ans = max(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n),n为fruits数组的长度
- 空间复杂度:O(1),任意时刻哈希表中最多只有3个键值对
1695. 删除子数组的最大得分
https://leetcode.cn/problems/maximum-erasure-value/description/
-
变长滑动窗口
- 在数组中找元素各不相同且所有元素之和最大子数组,返回该子数组的和。
class Solution { public: int maximumUniqueSubarray(vector<int>& nums) { unordered_map<int, int> mp; int ans = 0, sum = 0; for (int left = 0, right = 0; right < nums.size(); right++) { int t_right = nums[right]; mp[t_right]++; sum += t_right; while (mp[t_right] > 1) { // 元素各不相同的状态由右端加入的元素破坏,因此只关注右端元素 mp[nums[left]]--; sum -= nums[left]; left++; } ans = max(ans, sum); } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(m),m为不同元素的数量
2958. 最多 K 个重复元素的最长子数组
https://leetcode.cn/problems/length-of-longest-subarray-with-at-most-k-frequency/description/
-
变长滑动窗口
- 没啥好说的,标准的变长滑动窗口
class Solution { public: int maxSubarrayLength(vector<int>& nums, int k) { unordered_map<int, int> mp; int ans = 0; for (int left = 0, right = 0; right < nums.size(); right++) { int t = nums[right]; mp[t]++; while (mp[t] > k) { mp[nums[left++]]--; } ans = max(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(n)
2024. 考试的最大困扰度
https://leetcode.cn/problems/maximize-the-confusion-of-an-exam/description/
-
变长滑动窗口
- 这道题相比前几道题增加的复杂度在于既允许将T改为F,由允许将F改为T。
- 一个最直接的思路就是使用两次遍历,分别查看最长连续F和最长连续T,返回更大的长度
class Solution { public: int maxConsecutiveAnswers(string answerKey, int k) { // 1. 只考虑连续T的情况,即窗口中F的数量不能超过k int ans1 = 0, f_cnt = 0; for (int left = 0, right = 0; right < answerKey.length(); right++) { int c = answerKey[right]; if (c == 'F') f_cnt++; while (f_cnt > k) { int ch = answerKey[left]; if (ch == 'F') f_cnt--; left++; } ans1 = max(ans1, right - left + 1); } // 2. 只考虑连续F的情况,即窗口中T的数量不能超过k int ans2 = 0, t_cnt = 0; for (int left = 0, right = 0; right < answerKey.length(); right++) { int c = answerKey[right]; if (c == 'T') t_cnt++; while (t_cnt > k) { int ch = answerKey[left]; if (ch == 'T') t_cnt--; left++; } ans2 = max(ans2, right - left + 1); } return max(ans1, ans2); } };- 将求最长连续T和最长连续F结合起来,只进行一次遍历
class Solution { public: int maxConsecutiveAnswers(string answerKey, int k) { // 1. 只考虑连续T的情况,即窗口中F的数量不能超过k int ans = 0, cnt[2]{}, left = 0; for (int right = 0; right < answerKey.length(); right++) { int c = answerKey[right]; if (c == 'F') cnt[0]++; else cnt[1]++; while (cnt[0] > k && cnt[1] > k) { // T和F只要有一个的数量不大于k即可 int ch = answerKey[left++]; if (ch == 'F') cnt[0]--; else cnt[1]--; } ans = max(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
1004. 最大连续1的个数 III
https://leetcode.cn/problems/max-consecutive-ones-iii/description/
-
变长滑动窗口
- 思路很直接,就是满足窗口内0的个数不超过k的最大窗口长度。
class Solution { public: int longestOnes(vector<int>& nums, int k) { int ans = 0, cnt = 0, left = 0; for (int right = 0; right < nums.size(); right++) { if (nums[right] == 0) cnt++; while (cnt > k) { if (nums[left++] == 0) cnt--; } ans = max(ans, right - left + 1); } return ans; } };- 优化一下代码,用
1-nums[i]替代条件判断
class Solution { public: int longestOnes(vector<int>& nums, int k) { int ans = 0, cnt = 0, left = 0; for (int right = 0; right < nums.size(); right++) { cnt += 1 - nums[right]; while (cnt > k) { cnt -= 1 - nums[left++]; } ans = max(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
1658. 将 x 减到 0 的最小操作数
https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/description/
-
逆向思维+变长滑动窗口
- 这道题如果按正向思维会很麻烦,因为必须从两端不断取元素
- 而将其反过来思考就变成了一道典型的变长滑动窗口题
class Solution { public: int minOperations(vector<int>& nums, int x) { // 每次移除nums最左边或最右边的元素 // 逆向思维,将问题转化为从数组中找到窗口内元素之和为sum-x的最大窗口 int target = reduce(nums.begin(), nums.end()) - x; if (target < 0) return -1; int ans = -1, left = 0, sum = 0, n = nums.size(); for (int right = 0; right < n; right++) { sum += nums[right]; while (sum > target) { sum -= nums[left++]; } if (sum == target) ans = max(ans, right - left + 1); } return ans < 0 ? -1 : n - ans; } };
2025.4.11 继续做滑动窗口题,下面题目的目标是最短/最小
209. 长度最小的子数组
https://leetcode.cn/problems/minimum-size-subarray-sum/description/
-
变长滑动窗口(最短)
- 和目标为最长的滑动窗口题差不多,注意条件
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { int n = nums.size(), ans = n + 1, left = 0, sum = 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; } };- 时间复杂度:O(n)
- 空间复杂度:O(1)
2904. 最短且字典序最小的美丽子字符串
https://leetcode.cn/problems/shortest-and-lexicographically-smallest-beautiful-string/description/
-
变长滑动窗口:
- 分为两步:从串s中找到所有的美丽子串,然后从所有美丽子串中找到最短&&字典序最小的那个并返回。
- 找美丽字串可以通过变长滑动窗口实现;找最短&&字段序最小需要使用字符串的比较来实现
class Solution { public: string shortestBeautifulSubstring(string s, int k) { // 1. 找到所有的美丽子字符串 -> 变长滑动窗口 // 2. 从所有美丽子字符串中找出 最短 && 字典序最小 的那个 -> 比较 string ans{}; int left = 0, n = s.length(), curShort = n + 1, cntOnes = 0; for (int right = 0; right < n; right++) { cntOnes += s[right] - '0'; while (cntOnes - (s[left] - '0') >= k) { // 注意这里是区别于求最大/最长的地方 cntOnes -= s[left++] - '0'; } if (cntOnes == k) { int curLen = right - left + 1; string curStr = s.substr(left, curLen); if (curLen < curShort) { ans = curStr; curShort = curLen; } else if (curLen == curShort && curStr < ans) { ans = curStr; } } } return ans; } };- 时间复杂度:O(n^2),外层的遍历+内存求子串和字符串的比较
- 空间复杂度:O(1)
1234. 替换子串得到平衡字符串
https://leetcode.cn/problems/replace-the-substring-for-balanced-string/description/
-
变长滑动窗口:
- 想要通过替换子串得到平衡字串,字符串s中除子串外的其他部分各字符数量不能超过m = n / 4,否则无法通过替换该子串来得到平衡子串
- 这就是这道题目的难点,即将替换子串得到平衡子串这个问题转换为子串之外的各字符数量不超过m
- 可以使用哈希表,这里使用数组,因为快一点
class Solution { public: int balancedString(string s) { // 找最短需要替换的子串 // 条件:当前窗口外的QWER的个数均不超过1/4 int n = s.length(), cnt['x']{}; int m = n / 4, left = 0, ans = n; for (auto c : s) { ++cnt[c]; } if (cnt['Q'] <= m && cnt['W'] <= m && cnt['E'] <= m && cnt['R'] <= m) return 0; for (int right = 0; right < n; right++) { --cnt[s[right]]; while (cnt[s[left]] + 1 <= m) { ++cnt[s[left++]]; } if (cnt['Q'] <= m && cnt['W'] <= m && cnt['E'] <= m && cnt['R'] <= m) ans = min(ans, right - left + 1); } return ans; } };- 时间复杂度:O(n),n为字符串s的长度
- 空间复杂度:O(1)
这道题我无法AC的问题在于ans的初值设置为0。再就是要学会使用数组作为哈希表的方法。
专业之外,喜欢阅读,尤爱哲学、金庸、马尔克斯。

浙公网安备 33010602011771号