跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

滑动窗口算法深度解析:LeetCode 经典例题实战

通过五个 LeetCode 经典例题(最小操作数、水果成篮、字母异位词、串联所有单词的子串、最小覆盖子串),详细讲解了滑动窗口算法的核心思想与实现技巧。内容涵盖暴力解法优化、双指针移动策略、哈希表计数维护以及边界条件处理,提供完整的 C++ 代码示例与复杂度分析,帮助读者掌握滑动窗口在字符串和数组问题中的应用。

漫步发布于 2026/3/29更新于 2026/9/977 浏览
滑动窗口算法深度解析:LeetCode 经典例题实战

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

题目链接

题目描述:

给你一个整数数组 nums 和一个整数 x。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要修改数组以供接下来的操作使用。 如果可以将 x 恰好减到 0,返回最小操作数;否则,返回 -1。

示例 1:

输入 nums = [1,1,4,2,3], x = 5 输出:2 解释:最佳解决方案是移除后两个元素,将 x 减到 0。

示例 2:

输入 nums = [5,6,7,8,9], x = 4 输出:-1

示例 3:

输入 nums = [3,2,20,1,1,3], x = 10 输出:5 解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0。

对于示例 1,最优的操作次数是 2 次。对于示例 2,不论是从左边还是右边进行删除操作,x 永远不可能到 0,所以返回 -1。

我们将问题进行转换:原来要求的是从最左边或者最右边进行删除元素,直到 x 变成 0。我们可以将问题区域划分,左边的区域是 a,右边的区域是 b,a 区域和 b 区域里面的数值加起来等于 x。那么中间剩下的就是我们整个数组的值减去 x。题目让我们求出最小的操作次数,也就是说 a+b 最短的情况,换种思路,就是中间区域最长的情况,即 sum-x。我们只要求出整个数组中和为 sum-x 的区域的最长值就行了。

找出最长的子数组的长度,子数组的所有元素的和正好等于 sum-x。

做这种题我们可以想对立面。我们可以先想想暴力解法:设置两个指针,左和右,一开始指向起点,让右指针进行移动操作,直到两个指针的区域间的值大于等于 sum-x,我们就更新下我们的操作次数,然后移动左指针继续进行更新的操作。

当左指针往右边移动了,右指针就没有必要往左边进行移动了,因为左指针向右移动过后,区间中的元素大小更小了,右指针只能往右移动。

在这个暴力解法的基础上进行优化:

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int sum = 0;
        for (int a : nums) {
            sum += a;
        }
        int target = sum - x;
        // 细节问题,target 的值不能小于 0
        if (target < 0) return -1;
        int ret = -1;
        for (int left = , right = , tmp = ; right < nums.(); right++) {
            
            tmp += nums[right]; 
             (tmp > target) { 
                tmp -= nums[left];
                left++; 
            }
             (tmp == target) { 
                
                ret = (ret, right - left + );
            }
        }
         (ret == )  ret; 
          nums.() - ret; 
    }
};
0
0
0
size
// tmp 来计算左右指针中间的值的大小
// 进窗口的操作
while
// 进行判断,如果区间中的值大于了 target 我们就停下来
// 出窗口
if
// 更新结果
// 更新我们两个指针中间的长度
max
1
if
-1
return
// 没有找到
else
return
size
// 找到的话我们直接返回总长度减去中间的值

13. 水果成篮

题目链接

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果种类。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有两个篮子,并且每个篮子只能装单一类型的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从每棵树(包括开始采摘的树)上恰好摘一个水果。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits,返回你可以收集的水果的最大数目。

示例 1:

输入:fruits = [1,2,1] 输出:3 解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2] 输出:3 解释:可以采摘 [1,2,2] 这三棵树。如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2] 输出:4 解释:可以采摘 [2,3,2,2] 这四棵树。如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:可以采摘 [1,2,1,1,2] 这五棵树。

我们现在有两个篮子进行摘水果,然后每个篮子只能装一个水果,不能装多种水果。按实例 2 来说,示例二我们先采摘 0 号水果,然后再采摘 1 号水果,然后我们就不能采摘了,因为再后面的 2 号水果和 1 号水果种类不同,所以我们是只能采摘 2 棵树,但是我们如果从 1 号开始采摘的话,我们然后可以摘 2 号水果,我们再还能接着采摘 2 号水果,所以我们这种方案是可以采摘 3 棵树的。

就是我们的两个篮子只能装两种类型的水果,就是一个区间只能存在两种数字,不能存在第三种数字。

转化: 找出一个最长的子数组,子数组里面不超过两种类型的水果。

暴力解法:直接将所有的子数组都给找出来,找出其中最长的一个。我们可以借助一个哈希表来记录当前出现水果种类的次数。

我们现在定义两个指针,左和右,两个指针都从最开始的位置行动,右指针保持运行,直到我们水果的种类大于 2 了,我们就停下来,那么此时左和右中间的区域就是我们要求的。然后我们的左指针往右运行,那么我们就得想想我们的右指针回不回来呢?是不是和左指针重新开始还是说直接从上次的位置开始。

我们的左指针往右运行,那么我们水果种类只能出现两种:不变或者是变小,绝不可能变大,因为我们左往右走了一步,水果少了一个。

如果这个在左指针运行后,我们水果的种类不变的话,我们右指针就没必要回退了。

如果水果种类减少的话,那么我们区间里面还是只存在一种类型的结果,我们的右指针也是没有必要回去的,右指针直接右移。

那么我们就可以使用滑动窗口进行解答了。

进窗口的操作就是将我们右指针指向的元素丢到哈希表中。如果这个哈希表的长度大于 2 的话,我们就进行出窗口的操作。在哈希表中,我们不仅仅存入这个水果的种类,还要存入水果的数量。

在出窗口的时候我们还要对我们哈希表中的元素进行判断,如果某种种类的水果数量变成了 0 的话,那么我们要将这个水果从哈希表中删除的。

下面是我们的代码:

class Solution {
public:
    int totalFruit(vector<int>& f) {
        unordered_map<int, int> hash; // 统计窗口内出现多少种水果
        int ret = 0;
        for (int left = 0, right = 0; right < f.size(); right++) {
            hash[f[right]]++; // 进窗口
            while (hash.size() > 2) { // 进行判断,查看我们哈希表中的水果种类是否大于 2
                // 出窗口操作
                hash[f[left]]--;
                // 因为我们当前 left 要往右边走,那么我们就得判断下我们这个减少数量的这个水果的个数是否变成了 0
                if (hash[f[left]] == 0) { // 如果这个水果数量变成了 0 的话,那么我们就得将这个水果从哈希表中删除了
                    hash.erase(f[left]); // 将这个种类变为 0 的水果从哈希表中删除
                }
                left++; // 左指针往右边走
            }
            ret = max(ret, right - left + 1); // 更新结果,结果就是我们左右指针中间的区间的长度
        }
        return ret;
    }
};

但是我们发现我们的这个时间复杂度不行,因为我们哈希表中插入删除元素。所以我们这里是可以进行优化的操作的,对当前的哈希表进行优化操作。

我们可以直接利用一个数组来创建哈希表,并且增加一个变量 kinds 来表示我们水果的种类。下面我们就是直接利用数组进行操作。

class Solution {
public:
    int totalFruit(vector<int>& f) {
        int hash[100001] = {0}; // 统计窗口内出现多少种水果
        int ret = 0;
        for (int left = 0, right = 0, kinds = 0; right < f.size(); right++) {
            // 如果当前 right 指针指向的水果的个数为 0 的话,我们就种类++
            if (hash[f[right]] == 0) kinds++; // 维护水果的种类
            hash[f[right]]++; // 进窗口
            while (kinds > 2) { // 进行判断,查看我们哈希表中的水果种类是否大于 2
                // 出窗口操作
                hash[f[left]]--;
                // 因为我们当前 left 要往右边走,那么我们就得判断下我们这个减少数量的这个水果的个数是否变成了 0
                if (hash[f[left]] == 0) { // 如果这个水果数量变成了 0 的话,我们直接让种类减一
                    kinds--;
                }
                left++; // 左指针往右边走
            }
            ret = max(ret, right - left + 1); // 更新结果,结果就是我们左右指针中间的区间的长度
        }
        return ret;
    }
};

14. 找到字符串中所有字母异位词

题目链接

给定两个字符串 s 和 p,找到 s 中所有 p 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 1:

输入:s = "cbaebabacd", p = "abc" 输出:[0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入:s = "abab", p = "ab" 输出:[0,1,2] 解释: 起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。 起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。 起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

我们需要快速判断两个字符串是异位词,我们可以通过比较两个字符串来比较判断是否是异位词。

操作方式就是利用哈希表来进行计数。

下方就是暴力解法:不断的比较。一次移动 len 个子串然后和另一个子串进行比较,那么我们这里是可以用滑动窗口进行解答的。

利用 count 来统计窗口中有效字符的个数。进窗口后我们考虑下我们进窗口的这个数据是否是有效的数据,这个字母的出现个数是否跟另一个字符串里面对应字母的出现次数相等。

出窗口之前我们判断下我们出去这个字符是否是有效字符,是有效字符的话那么我们就进行 count-- 操作。

然后我们更新结果。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int hash1[26] = {0}; // 统计字符串 p 中每个字符出现的个数
        for (auto ch : p) {
            hash1[ch - 'a']++; // 对应字符的下标,统计对应字母出现的次数
        }
        int hash2[26] = {0}; // 统计窗口里面的每一个字符出现的个数
        int m = p.size();
        for (int left = 0, right = 0, count = 0; right < s.size(); right++) {
            char in = s[right];
            // 进窗口
            hash2[in - 'a']++; // 统计对应字符出现的个数
            if (hash2[in - 'a'] <= hash1[in - 'a']) count++; // 如果 s 中的这个字符的出现次数小于 p 中字符出现的个数的话,那么有效字符要++的
            if (right - left + 1 > m) { // 判断,这个就是窗口大了,我们得进行出窗口的操作
                // 出窗口&&维护 count
                char out = s[left++]; // 将这个元素丢出去,将这个元素丢出去,我们的 left 往右走
                if (hash2[out - 'a'] <= hash1[out - 'a']) count--; // 如果满足条件的话,那么就说明当前滑出去的是一个有效字符
                hash2[out - 'a']--;
            }
            // 更新
            if (count == m) ret.push_back(left); // 直接将起始位置的下标进行输出
        }
        return ret;
    }
};

说白了就是一直维持这个 count 的大小,然后知道 count 的大小等于这个 p 字符串的长度了,我们就将起始的左指针进行返回的操作。

15. 串联所有单词的子串

题目链接

给定一个字符串 s 和一个字符串数组 words。words 中所有字符串长度相同。

s 中的串联子串是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"],那么 "abcdef","abefcd","cdabef","cdefab","efabcd",和 "efcdab" 都是串联子串。"acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以任意顺序返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9] 解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。 子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。 子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。 输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[] 解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。 s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。 所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"] 输出:[6,9,12] 解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。 子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。 子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。 子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

我们可以将题目进行转换,在左边的字符串中找到我们右边的 a b 的异位词就行了,那么这个题就很像之前的那个找到字符串中所有字母异位词这个题了。

找到一个字符串就返回起始位置。

那么这个题我们就会使用到滑动窗口和哈希表了。

相较于找到字符串中所有字母异位词这个题的话,我们有三点不同:

  • 哈希表:之前的那题我们里面的哈希表存储的是一个个的字符,这里的话我们是一个又一个的字符串。
  • left 和 right 指针的移动:因为我们字符串数组里面的字符串的长度都是相等的,那么我们在移动的时候为了让新的字符串进入到窗口的话,那么我们每次移动的话是需要前进 len 个距离,len 就是我们单个字符串的长度。移动的步长是每个单词的长度。
  • 滑动窗口的执行次数:我们滑动窗口执行的次数是 len 次。因为我们的指针不仅仅是从第一个位置开始的,也可能从第二个位置,甚至第 len 个位置,如果从 len+1 的位置开始的话,那么就是重复了,和从第一个位置开始的情况重叠了,所以我们进行滑动窗口的次数是 len 次。
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ret;
        // 创建一个哈希表并且将单词在 word 中出现的次数进行记录
        unordered_map<string, int> hash1; // 保存 words 里面所有的单词的频次
        for (auto& w : words) hash1[w]++; // 记录 words 里面所有的单词出现的频次
        // 计算单词的长度
        int len = words[0].size(), m = words.size(); // 多少个单词
        // 我们这里的滑动窗口是需要执行 len 次的
        for (int i = 0; i < len; i++) { // 执行 len 次滑动窗口
            unordered_map<string, int> hash2; // 维护滑动窗口内单词的频次
            // 因为我们 right 在移动到对应的位置后,我们需要将 right+len 个字符放到滑动窗口里面去
            for (int left = i, right = i, count = 0; right + len <= s.size(); right += len) { // count 就是有效字符串的个数了
                // 进窗口 + 维护 count
                string in = s.substr(right, len); // 直接获取 right 后面 len 个字符,那么 in 就是一个字符串了
                hash2[in]++; // 我们将 in 这个字符出现的频次进行更新
                if (hash1.count(in) && hash2[in] <= hash1[in]) count++; // 如果我们 hash2 这个哈希中当前字符串出现的频次小于 hash1 的话,说明是有效单词,如果我们哈希 2 里面这个字符串出现次数大于哈希 1 的话,那么这个肯定是一个无效的单词,因为 hash1 中都没有进行记录
                // 判断
                if (right - left + 1 > len * m) { // 如果我们当前左右指针的范围大于我们要找的子串的长度的话,那么我们就进行出窗口操作
                    // 出窗口,维护 count
                    string out = s.substr(left, len); // 我们将 left 后面三个单词的范围的这个字符串拿出来
                    if (hash1.count(out) && hash2[out] <= hash1[out]) count--; // 有效单词减一
                    hash2[out]--;
                    // 出完窗口之后,我们的 left 需要向后移动
                    left += len;
                }
                // 更新结果
                if (count == m) ret.push_back(left); // 将我们
            }
        }
        // 当整个循环结束之后,我们 ret 中存储的就是结果了
        return ret;
    }
};

我们先创建一个哈希 1,用来存储我们 words 中的单词出现的次数,利用范围 for 获得每个单词出现的频次。

然后我们再计算 len 就是每个单词的长度,因为这个题中每个单词的长度都是一样的。

然后我们再来一个 for 循环,次数就是 len 次,因为我们的左指针有效循环的次数就是 len 次,如果是 len+1 次的话,那么结果是和第一次的结果是相同的。

在这个 for 循环中我们再来一个 for 循环进行进窗口的操作,在进窗口之前我们需要创建一个哈希 2 来记录我们滑动窗口内单词的频次。

这个内循环的我们需要维护好 count,就是窗口内有效单词的数量,如果没有在哈希 1 中出现过的,统统都是无效单词,出现过的才算是有效单词。

然后我们进行进窗口的操作,利用 substr 获取我们 right 后面 len 个范围的单词,将这个字符串放到 in 中,然后给 hash2 记录这个单词出现的次数,我们在进窗口后我们维护好我们的 count,如果当前的单词在 hash2 中出现的次数小于 hash1 中出现的次数的话,那么就证明这个单词是有效的,为什么呢?因为如果 hash2 中这个单词出现的次数大于 hash1 中的话,那么这个单词就是无效的,在 hash1 中不存在的,所以 hash2 中出现次数小于等于 hash1 中单词的次数的话,那么在这个单词就是有效的单词,那么我们 count++。

然后我们进行判断,如果我们当前的 right 和 left 的范围大于我们 words 中字符串组合成的字符串的长度的话,那么我们就进行出窗口的操作,我们就得将我们的 left 往右边进行移动了,还是利用 substr 获取我们 left 后面 len 个字符的字符串,将这个字符串存在 out 中,然后将 hash2 中的 out 这个字符串给减一,在减一之前我们还需要维护下我们的 count,对我们这个单词进行判断,是否是有效的单词。

出完窗口后,我们的 left 需要往右边进行移动,移动的距离是 len。

然后我们进行结果的更新,如果我们 count=m 的话,那么我们将此时的 left,就是这个对应字符串的开始位置存储在 vector 容器中去。

最后直接返回了。

但是吧,我们当前的这个效率比较慢,因为我们在判断当前的字符串是否是有效的字符串的时候,如果我们这个字符串 in 在 hash1 中不存在的话,那么我们就得创建 hash1[in] 了,所以这里是存在效率变低的,所以我们可以在判断条件中加上 hash1.count(in) 来判断我们的这个单词的数量,如果是 0 的话,我们直接退出了这个条件判断了。

16. 最小覆盖子串

题目链接

给你一个字符串 s、一个字符串 t。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入:s = "a", t = "aa" 输出:"" 解释:t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串。

只要我们的 s 中可以将 t 全部涵盖了的话,那么就是符合要求的。 使用暴力解决,如果 hash2 中的 ABC 的次数大于 hash1 中的次数的话,那么 hash2 里面存的这一段字符串就是有效的字符串了。

然后接下来从 D 这个位置接着枚举,就这种暴力解决的方法。

解法一:暴力枚举 + 哈希表

那么我们是否可以在这个解法的基础上进行优化呢?

我们在找到符合要求的一组之后,我们的 left 往右走,那么此时我们的 right 是没有必要回去的。

如果我们原先 left 指向的元素他的出现次数太多了或者是一个无效的,那么我们的 left 往右边移动一位,和此时 right 组成的区间依旧是有效的。

但是如果我们原先 left 指向的元素是一个有效字符并且出现次数的小于我们 hash1 中的次数,那么我们此时的区间就是不符合要求的,我们的 right 依旧不用回来,继续向后面走,直到找到了符合要求的那个元素为止。

那么优化的方案就是滑动窗口 + 哈希表。

当我们的窗口合法之后我们再进行出窗口的操作。

优化操作

我们使用 count 标记有效字符的种类,我们这里需要 hash2 中出现 hash1 中所有种类的字符,而不是个数,我们一定要按照有效字符种类来进行操作。

我们进窗口的时候维护一下 count,出窗口的时候维护一下 count,判断的时候判断一下 count。

class Solution {
public:
    string minWindow(string s, string t) {
        int hash1[128] = {0}; // 统计 t 字符串中每一个字符出现的频次
        int kinds = 0; // 统计有效字符有多少种
        for (auto ch : t) {
            // 如何判断我们当前的有效字符种类是否增加了呢?
            // 如果在进窗口前这个字符数量为 0 的话,并且是有效字符的话,那么我们的种类就多了一种了
            if (hash1[ch] == 0) kinds++; // 有效字符的种类加一
            hash1[ch]++;
        }
        // 出了循环我们就统计到了我们字符的种类数量
        int hash2[128] = {0}; // 统计窗口内每个字符的频次
        int minlen = INT_MAX, begin = -1;
        for (int left = 0, right = 0, count = 0; right < s.size(); right++) {
            char in = s[right]; // 获取我们这个字符的信息
            hash2[in]++; // 给这个字符的次数++
            // 判断我们加入的字符是否是有效字符
            // 进窗口 + 维护 count 变量
            if (hash2[in] == hash1[in]) count++; // 说明我们加入了一个有效的字符
            // 判断条件
            while (count == kinds) {
                // 更新结果
                if (right - left + 1 < minlen) { // 说明此时我们需要更新结果了
                    // 计算最小的子串的长度
                    minlen = right - left + 1;
                    // 开始的指针指向我们的 left
                    begin = left;
                }
                // 出窗口
                char out = s[left]; // 记录当前出窗口的单词
                left++; // 左指针往右边走
                // 维护 count
                if (hash2[out] == hash1[out]) count--; // 说明我们当前的字符是有效字符
                hash2[out]--; // 将当前出窗口的字符进行--操作
            }
        }
        if (begin == -1) return ""; // 如果我们开始的指针没有变的话,那么我们就直接返回空就行了
        else return s.substr(begin, minlen); // 如果变了的话,我们 begin 后面 minlen 个范围的字符串获取然后返回就行了
    }
};

我们先创建 hash1 来记录我们 t 中字符出现的次数,创建变量 kinds 记录种类,

然后我们遍历 t,获取我们的种类的数量,并且记录字符出现的频次。

然后我们创建 hash2 来记录我们窗口中的每个字符出现的频次。

创建变量 minlen 记录我们当前最小的子串长度,以及开始的指针 begin。

然后我们进入到滑动窗口的操作,我们先获取我们 right 指针的位置上的元素,并且在 hash2 中记录出现的次数。

然后我们在进窗口后判断下当前进的这个字符是否是有效的字符,并且进行维护我们的 count(字符种类)。

是否是有效的字符,我们依靠这段代码 hash2[in]==hash1[in],如果我们 hash2 中进的这个字符的次数等于我们 hash1 中字符的次数的话,那么我们就判定当前字符是有效的。

如果有效的话,那么我们当前的 count 就++。

进行判断,如果我们的 count 的大小等于我们的 kinds 的话,就是说明我们当前的窗口就是一个有效的窗口了。

那么我们就进行结果的更新操作。

记录我们当前区间的长度:right-left+1 并且记录我们当前的 begin。

然后我们再进行出窗口的操作,我们记录我们当前 left 位置的元素,然后从 hash2 中减少次数。

并且我们在减少次数之前我们需要判断下我们这个出窗口的元素是否是有效的字符,

最后出了循环,我们就返回对应的结果了。

目录

  1. 12. 将 x 减到 0 的最小操作数
  2. 13. 水果成篮
  3. 14. 找到字符串中所有字母异位词
  4. 15. 串联所有单词的子串
  5. 16. 最小覆盖子串

更多推荐文章

查看全部
  • 大语言模型微调技术详解与 LoRA 实践
  • 密码安全指南:从弱口令风险到多因素认证实践
  • Stable Diffusion WebUI 完整安装与实战指南
  • AI 编程工具 Cursor 核心功能与技术架构解析
  • Halcon 20.11 安装避坑指南与 License 配置详解
  • Streamlit 框架介绍:用 Python 快速构建数据应用
  • LangChain 大模型对话记忆模块实战与 Web 应用
  • Meta ShapeR:基于随机拍摄视频的 3D 物体生成技术
  • Java 自旋锁与读写锁详解
  • SLAM Toolbox 机器人定位与建图实战指南
  • 五大经典排序算法详解:插入、希尔、冒泡、选择与堆排序
  • Pandas 入门:Python 数据分析实战基础
  • 私有化部署 WebRTC:基于 aiortc 实现 Web 浏览器直接预览远程摄像头
  • HTML5+CSS3+JavaScript 实现高木同学圣诞树 GalGame 开发
  • WebApp 开发结构化方法:交互、功能与导航三大核心模型
  • C++ STL list 容器详解与模拟实现
  • node-llama-cpp 错误处理与调试:解决本地 AI 开发问题
  • 什么是人工智能?AI、机器学习与深度学习的关系
  • Llama-Factory 是否支持 FlashAttention 加速
  • 通过官方 API 搭建 QQ 群聊机器人

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online