滑动窗口训练

news2024/11/29 20:46:59

1.原理

我们用这道题目 LCR 008. 长度最小的子数组 来讲解“滑动窗口”的解法。

1.1.暴力解法

遍历每一个子数组(都要大于等于 7),最统计出最小的数组。

这样做的话,划分左右区间(leftright)就需要 O ( n 2 ) O(n^{2}) O(n2),然后每一个区间都要求和,所有求和又需要 O ( n ) O(n) O(n),整体时间复杂度就是 O ( n 3 ) O(n^{3}) O(n3)

1.2.优化一次

如何将上述解法优化呢?首先在求和上就可以优化到 O ( n ) O(n) O(n)

划分区间从 [left,left=right] 开始,先让 sum+arr[right],然后再 right++,这样子就不需要重复计算 sum 了,这样就可以优化为 O ( n 2 ) O(n^{2}) O(n2)

1.3.优化两次

全部都是正整数,也就是,加的数越多,得到的结果 sum 就越大(也就是具有单调性),因此当我们划分区间 [left,right] 后得到的 sum 只要比 target 大或等于就无需再 right++ 了,继续让其加加得到的数组肯定会比 target 大,但是数组长度也变大了,这是我们不需要的。

上述划分结束后,我们就得到了一个 [left,right] 窗口区间和对应的 sum,其中 [left,right-1]sum 一定小于 [left,right]sum 或者说 target

后面让这个窗口区间挪动起来,找比这个区间长度要小,但是窗口的 sum 大于等于 target 的,并且实时更新 maxLength

直接让 sum-arr[left],然后 left++ 即可得到 [left,right] 区间的新 sum

查看这个 sum 是否大于 target,如果是继续加加,如果不是就整个窗口移动,直到 right 到结尾。

而这种 leftright 双指针同向移动的情况就称为“滑动窗口”。

class Solution
{
public:
    int minSubArrayLen(int target, vector<int>& nums)
    {
        int sum = 0;
        int maxLength = 0;

        int left = 0;
        int right = 0;

        while (right < nums.size())
        {
            sum += nums[right];
            if (sum >= target)
            {
                break;
            }
            right++;
        }
        maxLength = right - left + 1;
        //得到窗口[left, right]
        
        if(sum < target)
        {
            return 0;
        }

        while(right < nums.size())
        {
            sum -= nums[left];
            left++;
            if (sum >= target)
            {
                maxLength = right - left + 1;
            }
            else//sum < target
            {
                right++;
                if (right >= nums.size())
                {
                    break;
                }
                sum += nums[right];
            }
        }
        return maxLength;
    }
};

或者写得更加高明一些:

class Solution
{
public:
    int minSubArrayLen(int target, vector<int>& nums)
    {
        int n = nums.size();
        int sum = 0;
        int len = INT_MAX;

        for(int left = 0, right = 0; right < n; right++)
        {
            sum += nums[right];
            while(sum >= target)
            {
                len = min(len, right - left + 1);
                sum -= nums[left++];
            }
        }
        return len == INT_MAX ? 0 : len;
    }
};

时间复杂度:由于只操作两个指针,最坏情况下操作 n + n n+n n+n 次,也就是 O ( n ) O(n) O(n)

因此可以看出,滑动窗口的题目基本都是:

  1. 设定窗口范围
  2. 让数据进窗口
  3. 判断是否符合要求,不符合则让数据出窗口

2.题目

2.1.LCR 016. 无重复字符的最长子串

2.1.1 暴力

在这里插入图片描述

时间复杂度: O ( n 2 ) O(n^{2}) O(n2)

2.1.2.优化

在这里插入图片描述

class Solution
{
public:
    int lengthOfLongestSubstring(string str)
    {
        int left = 0;
        int right = 0;
        int maxSize = 0;
        int hash[128] = { 0 };

        while (left < str.size())
        {
            while (right < str.size() && hash[str[right]] == 0)//不是重复的字符就放入哈希表之中
            {
                hash[str[right++]]++;
            }
            //走到这里说明出现了重复的字符,这个重复元素就是str[right]
            maxSize = maxSize > right - left ? maxSize : right - left;//记录最大长度
            while (str[left] != str[right])//让left直接跳到在和str[right]重复的元素后面
            {
                hash[str[left]]--;
                left++;
            }
            hash[str[left]]--;
            left++;//走到这里还要在加加一次
        }

        return maxSize;
    }
};

还可以写得更加简洁:

class Solution
{
public:
    int lengthOfLongestSubstring(string str)
    {
        int left = 0, right = 0, maxSize = 0, n = str.size(), hash[128] = { 0 };

        while (right < n)
        {
            hash[str[right]]++;//进窗口
            while (hash[str[right]] > 1)//判断
                hash[str[left++]]--;//出窗口

            maxSize = max(maxSize, right - left + 1);//记录最大长度
            right++;
        }

        return maxSize;
    }
};

时间复杂度: O ( n ) O(n) O(n)

2.2.1004. 最大连续 1 的个数 III

如果转变思路为:一个区间内包含的 0 的个数不超过 K,求这样的区间的最大值。

在这里插入图片描述

class Solution
{
public:
    int longestOnes(vector<int> nums, int K)
    {
        int left = 0, right = 0;
        int zero = 0;
        int maxSize = 0;
        int n = nums.size();
        
        while (right < n)
        {
            if (nums[right] == 0) zero++;

            while (zero > K)
            {
                if (nums[left] == 0) zero--;
                left++;
            }

            right++;
            maxSize = max(maxSize, right - left);
        }
        return maxSize;
    }
};

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

这道题目正面做比较困难,可以反面做。

在这里插入图片描述

class Solution
{
public:
    int minOperations(vector<int> nums, int x)
    {
        //1.设置 target
        int sum = 0;//整个数组求和
        for (auto e : nums) sum += e;
        int target = sum - x;//求出 target

        //2.处理特殊情况
        if (target < 0)//target 不可能小于 0
            return -1;

        //3.下面开始找到中间区间和为 target 即可
        int left = 0;
        int right = 0;
        int ret = -1;//这里之所以设置为 -1 是因为有可能出现找不到的情况(例如:左右端的数字均大于 target)
        int add = 0;

        while (right < nums.size())
        {
            //1.进窗口
            add += nums[right];

            //2.判断出窗口
            while (add > target)
            {
                add -= nums[left];
                left++;
            }

            //3.更新结果
            if (add == target)
            {
                ret = (int)max(ret, right - left + 1);
            }

            right++;
        }

        if (ret < 0)
            return ret;
        return nums.size() - ret;
    }
};

2.4.904. 水果成篮

依旧是使用滑动窗口,这个题比较简单。

class Solution
{
public:
    int totalFruit(vector<int>& fruits)
    {
        int left = 0, right = 0;
        unordered_map<int, int> um;
        int count = 0;

        while (right < fruits.size())
        {
            //1.进窗口
            um[fruits[right++]]++;

            //2.出窗口
            while (um.size() > 2)
            {
                um[fruits[left]]--;
                if (um[fruits[left]] == 0)
                    um.erase(fruits[left]);
                left++;
            }

            //3.更新
            if (um.size() == 2)
            {
                count = max(count, right - left);
            }
        }

        if (left == 0)
        {
            count = fruits.size();
        }

        return count;
    }
};

或者修改一下,简化代码:

class Solution
{
public:
    int totalFruit(vector<int>& fruits)
    {
        int left = 0, right = 0;
        unordered_map<int, int> um;
        int count = 0;

        while (right < fruits.size())
        {
            //1.进窗口
            um[fruits[right++]]++;

            //2.出窗口
            while (um.size() > 2)
            {
                um[fruits[left]]--;
                if (um[fruits[left]] == 0)
                {
                    um.erase(fruits[left]);
                }
                left++;
            }

            //3.更新
            count = max(count, right - left);
        }

        return count;
    }
};

但是这里的哈希删除效率比较低下,实际上我们可以根据题目特点(0 <= fruits[i] < fruits.length)来创建一个自己的哈希表,而不使用库内的哈希表,这也是一种做题技巧。

class Solution
{
public:
    int totalFruit(vector<int>& fruits)
    {
        int left = 0, right = 0;
        int um[100001] = { 0 };
        int count = 0;
        int kinds = 0;

        while (right < fruits.size())
        {
            //1.进窗口
            if(um[fruits[right]] == 0)
                kinds++;
            um[fruits[right++]]++;

            //2.出窗口
            while (kinds > 2)
            {
                um[fruits[left]]--;
                if(um[fruits[left]] == 0)
                    kinds--;
                left++;
            }

            //3.更新
            count = max(count, right - left);
        }

        return count;
    }
};

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

这一题的解法比较好理解,如果直接使用库内的 unordered_map 哈希容器,写起来是很快的,因为这题的窗口是固定的,但是需要注意越界的问题。

class Solution
{
public:
    vector<int> findAnagrams(const string& str, const string& sub)
    {
        //1.返回用的顺序表
        vector<int> ret;

        //2.匹配字符串的哈希表
        unordered_map<char, int> um_sub;
        for (auto ch : sub)
        {
            um_sub[ch]++;
        }

        //3.初始化对应匹配字符串长度的子串
        unordered_map<char, int> um_str_sub;
        for (int i = 0; i < sub.size(); i++)
        {
            um_str_sub[str[i]]++;
        }

        //4.判断子串和匹配字符串是否异位词
        for (int i = 0; i + sub.size() - 1 < str.size(); i++)
        {
            if (um_sub == um_str_sub)
            {
                ret.push_back(i);
            }

            //5.更新固定窗口
            um_str_sub[str[i]]--;
            if (um_str_sub[str[i]] == 0)
            {
                um_str_sub.erase(str[i]);
            }
            um_str_sub[str[i + sub.size()]]++;
        }

        //6.返回索引结果
        return ret;
    }
};

当然,我们还可以优化一下,如果窗口的长度和匹配字符串不相等,那就无需判断是否异位了,直接更新窗口即可。

class Solution
{
public:
    vector<int> findAnagrams(const string& str, const string& sub)
    {
        //1.返回用的顺序表
        vector<int> ret;
        if (sub.size() > str.size())//处理特殊情况
            return ret;

        //2.匹配字符串的哈希表
        unordered_map<char, int> um_sub;
        for (auto ch : sub)
        {
            um_sub[ch]++;
        }

        //3.初始化对应匹配字符串长度的子串
        unordered_map<char, int> um_str_sub;
        for (int i = 0; i < sub.size(); i++)
        {
            um_str_sub[str[i]]++;
        }

        //4.判断子串和匹配字符串是否异位词
        for (int i = 0; i + sub.size() - 1 < str.size(); i++)
        {
            if (um_sub.size() == um_str_sub.size() && um_sub == um_str_sub)
            {
                ret.push_back(i);
            }

            //5.更新固定窗口
            um_str_sub[str[i]]--;
            if (um_str_sub[str[i]] == 0)
            {
                um_str_sub.erase(str[i]);
            }
            um_str_sub[str[i + sub.size()]]++;
        }

        //6.返回索引结果
        return ret;
    }
};

还可以不用库里的哈希表,直接用我们自己的用于映射的数组。

class Solution
{
public:
    //判断两个 hash 是否相等
    bool equality(int* hash_str_sub, int* hash_sub)
    {
        for (int i = 0; i < 26; i++)
        {
            if (hash_str_sub[i] != hash_sub[i])
                return false;
        }
        return true;
    }

    vector<int> findAnagrams(const string& str, const string& sub)
    {
        //1.防止匹配字符串长于主串
        vector<int> ret;
        if (sub.size() > str.size())
            return ret;

        //2.映射匹配字符串的 hash
        int hash_sub[26] = { 0 };
        int count1 = 0;
        for (auto ch : sub)
        {
            if (++hash_sub[ch - 'a'] == 1)
                count1++;//统计有效字符串
        }

        //3.设定好主串的 hash
        int hash_str_sub[26] = { 0 };
        int count2 = 0;
        for (int i = 0; i < sub.size(); i++)
        {
            if (++hash_str_sub[str[i] - 'a'] == 1)
                count2++;//统计有效字符串
        }

        for (int i = 0; i + sub.size() - 1 < str.size(); i++)
        {
            //4.只有在有效字符串匹配的情况下才需要比较 hash 是否相等
            if (count2 == count1)
            {
                if (equality(hash_str_sub, hash_sub))
                    ret.push_back(i);
            }

            //5.保留之前的窗口,去除旧数据,新映射数据,让窗口走起来(注意避免越界)
            if (i + sub.size() < str.size() && --hash_str_sub[str[i] - 'a'] == 0)
                count2--;
            if (i + sub.size() < str.size() && ++hash_str_sub[str[i + sub.size()] - 'a'] == 1)
                count2++;
        }

        return ret;
    }
};

还可以将数组优化使用 vector,可以让代码检查越界的情况,但其效能未必输过数组。

class Solution
{
public:
    //判断两个 hash 是否相等
    bool equality(const vector<int>& hash_str_sub, const vector<int>& hash_sub)
    {
        for (int i = 0; i < 26; i++)
        {
            if (hash_str_sub[i] != hash_sub[i])
                return false;
        }
        return true;
    }

    vector<int> findAnagrams(const string& str, const string& sub)
    {
        //1.防止匹配字符串长于主串
        vector<int> ret;
        if (sub.size() > str.size())
            return ret;

        //2.映射匹配字符串的 hash
        vector<int> hash_sub(26, 0);
        int count1 = 0;
        for (auto ch : sub)
        {
            if (++hash_sub[ch - 'a'] == 1)
                count1++;//统计有效字符串
        }

        //3.设定好主串的 hash
        vector<int> hash_str_sub(26, 0);
        int count2 = 0;
        for (int i = 0; i < sub.size(); i++)
        {
            if (++hash_str_sub[str[i] - 'a'] == 1)
                count2++;//统计有效字符串
        }

        for (int i = 0; i + sub.size() - 1 < str.size(); i++)
        {
            //4.只有在有效字符串匹配的情况下才需要比较 hash 是否相等
            if (count2 == count1)
            {
                if (equality(hash_str_sub, hash_sub))
                    ret.push_back(i);
            }

            //5.保留之前的窗口,去除旧数据,新映射数据,让窗口走起来(注意避免越界)
            if (i + sub.size() < str.size() && --hash_str_sub[str[i] - 'a'] == 0)
                count2--;
            if (i + sub.size() < str.size() && ++hash_str_sub[str[i + sub.size()] - 'a'] == 1)
                count2++;
        }

        //6.返回索引结果
        return ret;
    }
};

当然也可以使用我们之前的滑动数组 进窗口-判断条件(出窗口)-更新结果 的模式来做。

class Solution
{
public:
    //判断两个 hash 是否相等
    bool equality(const vector<int>& hash_str_sub, const vector<int>& hash_sub)
    {
        for (int i = 0; i < 26; i++)
        {
            if (hash_str_sub[i] != hash_sub[i])
                return false;
        }
        return true;
    }

    vector<int> findAnagrams(const string& str, const string& sub)
    {
        //1.防止匹配字符串长于主串
        vector<int> ret;
        if (sub.size() > str.size())
            return ret;

        //2.映射匹配字符串的 hash
        vector<int> hash_sub(26, 0);
        int count1 = 0;
        for (auto ch : sub)
        {
            if (++hash_sub[ch - 'a'] == 1)
                count1++;//统计有效字符串
        }

        vector<int> hash_str_sub(26, 0);
        int count2 = 0;

        //3.初始化窗口
        int left = 0;
        int right = 0;

        while (right < str.size())
        {
            //4.入窗口
            while (right - left < sub.size())
            {
                if (++hash_str_sub[str[right] - 'a'] == 1)
                    count2++;//统计有效字符串
                right++;
            }

            //5.判断条件,更新结果
            if (count1 == count2 && equality(hash_str_sub, hash_sub))
                ret.push_back(left);

            //6.出窗口
            if (--hash_str_sub[str[left++] - 'a'] == 0)
                count2--;
            if (right + 1 < str.size() && ++hash_str_sub[str[right++] - 'a'] == 1)
                count2++;
        }

        //7.返回索引结果
        return ret;
    }
};

补充:但是我在力扣上测试,貌似使用数组会更快一些?不过这两者在实际中效率真的不会差太多,这里我也放入数组版本的。

class Solution
{
public:
   //判断两个 hash 是否相等
   bool equality(int* hash_str_sub, int* hash_sub)
   {
       for (int i = 0; i < 26; i++)
       {
           if (hash_str_sub[i] != hash_sub[i])
               return false;
       }
       return true;
   }

   vector<int> findAnagrams(const string& str, const string& sub)
   {
       //1.防止匹配字符串长于主串
       vector<int> ret;
       if (sub.size() > str.size())
           return ret;

       //2.映射匹配字符串的 hash
       int hash_sub[26] = { 0 };
       int count1 = 0;
       for (auto ch : sub)
       {
           if (++hash_sub[ch - 'a'] == 1)
               count1++;//统计有效字符串
       }

       int hash_str_sub[26] = { 0 };
       int count2 = 0;

       //3.初始化窗口
       int left = 0;
       int right = 0;

       while (right < str.size())
       {
           //4.入窗口
           while (right - left < sub.size())
           {
               if (++hash_str_sub[str[right] - 'a'] == 1)
                   count2++;//统计有效字符串
               right++;
           }

           //5.判断条件,更新结果
           if (count1 == count2 && equality(hash_str_sub, hash_sub))
               ret.push_back(left);

           //6.出窗口
           if (--hash_str_sub[str[left++] - 'a'] == 0)
               count2--;
           if (right + 1 < str.size() && ++hash_str_sub[str[right++] - 'a'] == 1)
               count2++;
       }

       //7.返回索引结果
       return ret;
   }
};

这里必须提一下,我们在写面试的笔试题目时,一定要先用库函数和库容器,保证自己的思路和解法清晰,在尝试能否进行优化(更换功能过重的库函数和库容器、排除特殊情况)

如果写得更加彻底一些,还可以直接根据有效字符判断两个哈希是否异位,而无需走固定的 26 次比较。

class Solution
{
public:
    vector<int> findAnagrams(const string& str, const string& sub)
    {
        vector<int> ret;

        //1.映射匹配字符串的 hash1
        int hash1[26] = { 0 };
        for (auto ch : sub) hash1[ch - 'a']++;

        //2.映射主串的 hash
        int hash2[26] = { 0 };
        int len = sub.size();
        for (int left = 0, right = 0, count = 0; right < str.size(); right++)
        {
            //进窗口
            char in = str[right];
            hash2[in - 'a']++;
            if (hash2[in - 'a'] <= hash1[in - 'a']) count++;//统计有效字符个数
        
            //出窗口
            if (right - left + 1 > len)//以前我们是用循环的,由于窗口固定不变,因此可以直接用 if
            {
                char out = str[left++];
                if (hash2[out - 'a'] <= hash1[out - 'a']) count--;
                hash2[out - 'a']--;
            }

            //更新结果
            if (count == len) ret.push_back(left);
        }

        //3.返回索引结果
        return ret;
    }
};

从上面这种解法来看,两个动静态哈希判断相等未必需要遍历元素,在动态映射的过程中,可以使用变量记录映射后的值是否和另外一个静态映射后的值是否相等或者小于(然后统计这样的值的个数),插入完毕后,再查看这个个数和静态哈希的原数组是否相同。

这个结论很重要,可以提高我们比较两个动静态哈希的比较速度。

2.6.30. 串联所有单词的子串

class Solution
{
public:
    vector<int> findSubstring(const string& str, const vector<string>& subs)
    {
        vector<int> ret;
        if(subs.size() * subs[0].size() > str.size()) //排除特殊情况,提高一点效率
            return ret;

        //1.映射匹配字符串的 hash1
        //int hash1[26] = { 0 };
        unordered_map<string, int> hash1;

        //for (auto ch : sub) hash1[ch - 'a']++;
        for (auto str : subs)
            hash1[str]++;

        //2.映射主串的 hash
        //int hash2[26] = { 0 };
        //hash2 转移到了循环内,局部的哈希表可以达到自动清理资源的目的
        //如果 hash2 写在这里就需要在内循环执行一次后使用 hash2.clean()

        //int len = sub.size();
        int len = subs.size();
        int n = subs[0].size();

        for (int index = 0; index < n; index++)
        {
            unordered_map<string, int> hash2;
            for (int left = index, right = index, count = 0; right + n <= str.size(); right += n)
            {
                //进窗口
                //char in = str[right];
                string in = str.substr(right, n);

                //hash2[in - 'a']++;
                hash2[in]++;

                //if (hash2[in - 'a'] <= hash1[in - 'a']) 
                //  count++;//统计有效字符个数
                if (hash1.count(in) && hash2[in] <= hash1[in]) //这里的 hash1.cout(in) 是一个优化,不加也可以,但是可以避免哈希表进行无意义的插入,这个技巧值得学习
                    count++;//统计有效字符个数

                //出窗口
                //if (right - left + 1 > len) //滑动窗口本来是用循环的,由于窗口固定不变,因此可以直接用 if
                if (right - left + 1 > len * n) //滑动窗口本来是用循环的,由于窗口固定不变,因此可以直接用 if
                {
                    //char out = str[left++];
                    string out = str.substr(left, n);
                    left += n;

                    //if (hash2[out - 'a'] <= hash1[out - 'a'])
                    //  count--;
                    if (hash1.count(out) && hash2[out] <= hash1[out]) //这里的 hash1.cout(in) 是一个优化,不加也可以,但是可以避免哈希表进行无意义的插入,这个技巧值得学习
                        count--;

                    //hash2[out - 'a']--;
                    hash2[out]--;
                }

                //更新结果
                if (count == len)
                    ret.push_back(left);
            }
        }

        //3.返回索引结果
        return ret;
    }
};

2.7.LCR 017. 最小覆盖子串

2.7.1.使用个数的 count

class Solution
{
public:
	string minWindow(string s, string t)
	{
		string ret = s;
		int flag = 0;

		//1.设置哈希表
		int hash1[128] = { 0 };
		for (auto it : t)
			hash1[it]++;

		int hash2[128] = { 0 };
		int count = 0;

		int len = t.size();
		for (int left = 0, right = 0; right < s.size(); right++)
		{
			//2.进窗口
			hash2[s[right]]++;
			if (hash2[s[right]] <= hash1[s[right]])
				count++;

			//3.判断(出窗口)
			while (count == len)
			{
                //更新
				if (ret.size() > s.substr(left, right - left + 1).size())
					ret = s.substr(left, right - left + 1);

				if (hash2[s[left]]-- <= hash1[s[left++]])
				{
					flag = 1;
					count--;
				}
			}
		}

		if(flag == 1)
			return ret;
		return "";
	}
};

这里有不少优化的地方,实际上我们可以不统计有效字符的个数,只需要统计子串中有效字符的种类,然后判断其种类是否和匹配字符串 t 中字符的种类一样多,一样多就代表是被最小覆盖的。

2.7.2.使用种类的 count

class Solution
{
public:
	string minWindow(string s, string t)
	{
		string ret = s;
		int flag = 0;

		//1.设置哈希表
        int kinds = 0;
		int hash1[128] = { 0 };
		for (auto it : t)
			if(hash1[it]++ == 0)
                kinds++;

		int hash2[128] = { 0 };
		int count = 0;

		for (int left = 0, right = 0; right < s.size(); right++)
		{
			//2.进窗口
			hash2[s[right]]++;
			if (hash2[s[right]] == hash1[s[right]])
				count++;

			//3.判断(出窗口)
			while (count == kinds)
			{
                //更新
				if (ret.size() > s.substr(left, right - left + 1).size())
					ret = s.substr(left, right - left + 1);

				if (hash2[s[left]]-- <= hash1[s[left++]])
				{
					flag = 1;
					count--;
				}
			}
		}

		if(flag == 1)
			return ret;
		return "";
	}
};

2.7.3.去除字符串的冗余提取

生成子串其实只需要知道起始索引 begin 和长度 minlen 即可,因此我们可以在最后返回的时候提取字符串,而不是直接在更新条件中提取(这样会很慢,我们的滑动窗口思路本身效率很高,完全是被这个冗余的子串提取影响了效率),在更新的时候,只需要更新新子串的 beginminlen 即可。

其中进出窗口的元素最好还是写明一下(incout),提高代码可读性,并且根据 begin 是否被更改来替代标记位 flag

class Solution
{
public:
	string minWindow(string s, string t)
	{
		string ret = s;

		//1.设置哈希表
        int kinds = 0;
		int hash1[128] = { 0 };
		for (auto it : t)
			if(hash1[it]++ == 0)
                kinds++;

		int hash2[128] = { 0 };

        int begin = -1, minlen = INT_MAX;
		for (int left = 0, right = 0, count = 0; right < s.size(); right++)
		{
			//2.进窗口
            char in = s[right];
			if (++hash2[in] == hash1[in])
				count++;

			//3.判断(出窗口)
			while (count == kinds)
			{
                //更新
				if (right - left + 1 < minlen)
                {
					minlen = right - left + 1;
                    begin = left;
                }

                char out = s[left++];
				if (hash2[out]-- == hash1[out])
					count--;
			}
		}

        if(begin == -1)
            return "";
	    return ret.substr(begin, minlen);
	}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1318149.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎样长时间保持SSH会话连接不断开?

操作场景 使用SSH方式登录CentOS Stream操作系统的云服务器时&#xff0c;过一段时间就会自动断开连接。 该文档适用于CentOS/EulerOS系统。 操作方法 编辑/etc/ssh/sshd_config文件设置心跳&#xff0c;保持连接。 编辑/etc/ssh/sshd_config&#xff0c;添加配置项&#x…

Flink系列之:监控反压

Flink系列之&#xff1a;监控反压 一、反压二、Task 性能指标三、示例四、反压状态 Flink Web 界面提供了一个选项卡来监控正在运行 jobs 的反压行为。 一、反压 如果你看到一个 task 发生 反压警告&#xff08;例如&#xff1a; High&#xff09;&#xff0c;意味着它生产数…

Android动画

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、动画实现3.1 帧动画资源文件中实现…

Linux线程的设计

文章目录 一.理解Linux线程的本质进程地址空间是进程访问系统资源的窗口Linux系统中,线程是比进程更轻量级的执行流 二.Linux线程独立运行的原理三.基础线程控制 一.理解Linux线程的本质 进程地址空间是进程访问系统资源的窗口 Linux系统中,线程是比进程更轻量级的执行流 线程…

IIS如何本地部署网站,作为局域网内的服务器

文章目录 IIS本地部署WebService1.使用IIS及WebService的原因:2.相关文件说明及网络条件说明&#xff1a;&#xff08;1&#xff09;文件说明&#xff1a;&#xff08;2&#xff09;网络条件说明&#xff1a; 3.IIS安装与配置&#xff1a;第一步&#xff1a;安装第二步&#xf…

全国职业院校技能大赛“大数据应用开发”赛项说明

1、赛项介绍 &#xff08;1&#xff09;赛项名称 全 国 职 业 院 校 技 能 大 赛 “大数据应用开发” 赛 项 职业院校技能大赛官网 (vcsc.org.cn)https://www.vcsc.org.cn/ 大赛组织机构介绍 全国职业院校技能大赛(以下简称大…

Python 爬虫开发完整环境部署,爬虫核心框架安装

Python 爬虫开发完整环境部署 前言&#xff1a; ​ 关于本篇笔记&#xff0c;参考书籍为 《Python 爬虫开发实战3 》 笔记做出来的一方原因是为了自己对 Python 爬虫加深认知&#xff0c;一方面也想为大家解决在爬虫技术区的一些问题&#xff0c;本篇文章所使用的环境为&#x…

网络攻击1——网络安全基本概念与终端安全介绍(僵尸网路、勒索病毒、木马植入、0day漏洞)

目录 网络安全的基本术语 黑客攻击路径 终端安全 僵尸网络 勒索病毒 挖矿病毒 宏病毒 木马的植入 0day漏洞 流氓/间谍软件 网络安全的基本术语 网络安全的定义&#xff08;CIA原则&#xff09; 数据的保密性Confidentiality&#xff08;对称/非对称秘钥&#xff09; …

mysql innodb知识记录

官方文档 官网架构图 innodb 特性 内存 buffer pool 采用优化后的LRU算法&#xff0c; 3/8 of the buffer pool is devoted to the old sublist.The midpoint of the list is the boundary where the tail of the new sublist meets the head of the old sublist.When In…

C语言-Makefile

Makefile 什么是make&#xff1f; make 是个命令&#xff0c;是个可执行程序&#xff0c;用来解析 Makefile 文件的命令这个命令存放在 /usr/bin/ 什么是 makefile? makefile 是个文件&#xff0c;这个文件中描述了我们程序的编译规则咱们执行 make 命令的时候&#xff0c; m…

[DroneCAN]CAN-Convertor控制CAN电调电机

简介 CAN电调电机是一类通过CAN协议控制转速的电调电机&#xff0c;和传统的PWM电调电机不同在于&#xff0c;CAN协议有网络性和抗干扰性&#xff0c;因此其性能比PWM更好&#xff0c;占用的端口数也会更少。在apm或者px4等基于dronecan的飞控来说&#xff0c;想要控制第三方的…

【CMU 15-445】Lecture 10: Sorting Aggregations Algorithms 学习笔记

Sorting & Aggregations Algorithms SortingTop-N Heap SortExternal Merge Sort2-WAY External Merge SortK-WAY External Merge SortDouble Buffering Optimization AggregationsSortingHashing 本节课主要介绍的是数据库系统中的排序算法以及聚合算法 Sorting 排序算法…

大模型自定义算子优化方案学习笔记:CUDA算子定义、算子编译、正反向梯度实现

01算子优化的意义 随着大模型应用的普及以及算力紧缺&#xff0c;下一步对于计算性能的追求一定是技术的核心方向。因为目前大模型的计算逻辑是由一个个独立的算子或者说OP正反向求导实现的&#xff0c;底层往往调用的是GPU提供的CUDA的驱动程序。如果不能对于整个计算过程学习…

LearnDash LMS ProPanel在线学习系统课程创作者的分析工具

点击阅读LearnDash LMS ProPanel在线学习系统课程创作者的分析工具原文 LearnDash LMS ProPanel在线学习系统课程创作者的分析工具通过整合报告和作业管理来增强您的 LearnDash 管理体验&#xff0c;使您能够发送特定于课程的通信&#xff0c;并显示课程的实时活动&#xff01…

分类信息网商业运营版源码系统:适合各类行业分类站点建站 带安装部署教程

随着互联网的快速发展&#xff0c;信息分类网站在各个行业中得到了广泛应用。为了满足不同行业的需求&#xff0c;罗峰给大家分享一款适合各类行业分类站点建站的商业运营版源码系统。该系统旨在提供一套完整的解决方案&#xff0c;帮助用户快速搭建自己的分类信息网站&#xf…

【最新版】在WSL上运行 Linux GUI (图形用户界面)应用(Gnome 文本编辑器、GIMP、Nautilus、VLC、X11 应用)

文章目录 一、 安装WSL0. 先决条件1. 全新安装2. 现有 WSL 安装3. 注意事项 二、运行 Linux GUI 应用1. 更新发行版中的包2. 安装 Gnome 文本编辑器启动 3. 安装 GIMP启动 4. 安装 Nautilus启动 5. 安装 VLC启动 6. 安装 X11 应用 适用于 Linux 的 Windows 子系统 (WSL) 现在支…

Javaweb考前复习冲刺(不断更新版)

Javaweb考前复习冲刺 第一章&#xff1a; JavaWeb 入门 JavaWeb是指&#xff1a;以Java作为后台语言的项目工程。 javaweb项目创建的过程&#xff1a; 首先集成Tomcat服务器环境新建dynamic web project部署工程运行 路由含义&#xff1a; ​ http://localhost:8080/工程…

Redis 主从集群 —— 超详细操作演示!

五、Redis 主从集群 五、Redis 主从集群5.1 主从集群搭建5.1.1、伪集群搭建与配置5.1.2、分级管理5.1.3、容灾冷处理 5.2 主从复制原理5.2.1、主从复制原理5.2.2、数据同步演变过程 5.3 哨兵机制实现5.3.1 简介5.3.2 Redis高可用集群搭建5.3.3 Redis高可用集群的启动5.3.4 Sent…

ubuntu创建apt-mirror本地仓库

首先创建apt-mirror的服务端&#xff0c;也就是存储所有apt-get下载的文件和依赖。大约需要300G&#xff0c;预留400G左右空间就可以开始了。 安装ubuntu省略&#xff0c;用的是ubuntu202204 ubuntu挂载硬盘&#xff08;不需要的可以跳过&#xff09;: #下载挂载工具 sudo apt…

Java并发(十九)----Monitor原理及Synchronized原理

1、Java 对象头 以 32 位虚拟机为例 普通对象 |--------------------------------------------------------------| | Object Header (64 bits) | |------------------------------------|-------------------------| | Mark W…