力扣方法总结:滑动窗口

代码模板:

int left = 0; // 滑动窗口左指针
int right = 0; // 滑动窗口右指针
for (; right < nums.size(); right++)
{
	// 执行某项操作
    ...
    // 若不满足窗口条件
    while (condition not satisfied)
    {
    	// 左指针右移
		...
		left++;
    }
	...
}
...

3. 无重复字符的最长子串 Medium 滑动窗口、散列表 2021/11/10

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例:
输入: s = “pwwkew”
输出: 3

重要思想方法:滑动窗口,遍历字符串,左端位置默认为0,右端也默认为0,从右端向右逐渐滑动。

class Solution {
public:
    //右端-左端+1即为字符串长度,散列表中的元素即为拆分的字符串
    //如果没有遇到散列表中的字符,则加入散列表并继续右移
    //如果遇到散列表中的字符,将原有字符删除并左端向右滑动,直到该元素不出现在散列表中为止
    //时间复杂度仅为O(n),比暴力解法的O(n^3)快得多!
    int lengthOfLongestSubstring(string s) {
        if(s.size()==0) return 0;
        int max_length = 0;
        int left = 0; //左端元素位置
        int right = 0; //右端元素位置
        unordered_set<char> lookup; //散列表,用于存放元素
        for(; right < s.size(); right++)
        {
            while(lookup.find(s[right])!=lookup.end()) //如果在散列表中发现右端元素
            {
                lookup.erase(s[left]); //删除左端元素
                left++; //左端向右一格
            }
            max_length = max(right-left+1, max_length); //比较和最大长度之间的关系
            lookup.insert(s[right]); //将右端元素插入散列表
        }
        return max_length;
    }
};

713. 乘积小于 K 的子数组 Medium 滑动窗口 2022/5/7

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。
示例:
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。

滑动窗口,对于每次右指针所在的位置,right-left+1恰好等于当前以右指针为终点的子数组个数。如果乘积超过k,则左指针向右移动直到小于k。

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k <= 1) return 0;
        long mul = 1;
        int count = 0;
        int left = 0; 
        int right = 0;
        int n = nums.size();
        for (; right < n; right++)
        {
            mul = mul * nums[right];
            // 窗口过大,左指针收缩
            while (mul >= k)
            {
                mul = mul /= nums[left];
                left++;
            }
            count += right - left + 1; 
        }
        return count;
    }
};

209. 长度最小的子数组 Medium 滑动窗口 2023/1/29

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 窗口的左右区间
        int left = 0;
        int right = 0;
        int res = INT_MAX;
        // 初始和设为第一个数
        int sum = nums[0];
        // 套用滑动窗口模板
        while (right < nums.size()) {
            // 窗口太小 右指针右移
            if (sum < target) {
                right++;
                // 注意边界条件
                if (right >= nums.size()) break;
                sum += nums[right]; // 移完加上右指针的值
                continue;
            }
            // 窗口太大 左指针左移
            res = min(res, right - left + 1);
            sum -= nums[left];
            left++;
        }
        return res == INT_MAX ? 0 : res;
    }
};

下面为改进版滑动窗口,针对窗口大,左指针右移进行while循环直至窗口过小,且在大循环开始加上右指针的值,非常巧妙。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 窗口的左右区间
        int left = 0;
        int right = 0;
        int res = INT_MAX;
        // 初始和设为第一个数
        int sum = 0;
        // 套用滑动窗口模板
        while (right < nums.size()) {
            sum += nums[right];
            // 窗口太大 左指针右移
            if (sum >= target) {
                while (sum >= target) {
                    sum -= nums[left];
                    left++;
                }
                res = min(res, right - left + 2);
            }
            // 无论左指针是否右移 右指针都要右移
            right++;
        }
        return res == INT_MAX ? 0 : res;
    }
};

本题也可抓住其连续项和的特点,使用前缀和+二分法也可实现,但时间复杂度为O(nlogn),相较滑动窗口的O(n)稍差一些。
求出前缀和后,即找到两个最接近的元素差要求大于target即可,遍历一次每次使用二分法找到对应右区间并更新长度即可。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int res = INT_MAX;
        vector<int> preSum(n + 1, 0);
        for (int i = 0; i < n; i++) 
            preSum[i + 1] = preSum[i] + nums[i];
        // 遍历每个数为左区间,使用二分法找到其右区间
        for (int i = 0; i < preSum.size(); i++) {
            int targetNum = target + preSum[i];
            // 二分法
            int left = i;
            int right = preSum.size() - 1;
            while (left <= right) {
                int mid = (left + right) / 2;
                if (preSum[mid] < targetNum)
                    left = mid + 1;
                else right = mid - 1;
            }
            // 找不到右区间
            if (left >= preSum.size()) break;
            res = min(res, left - i);
        }
        return res == INT_MAX ? 0 : res;
    }
};

附二分查找模板,非常重要!!!

int searchInsert(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1;
    while (left <= right) {
        int mid = (right + left) / 2;
        if (nums[mid] == target) {
            return mid;
        }
        else if (nums[mid] < target) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    return left;
}

3. 无重复字符的最长子串 Medium 滑动窗口、散列表 2021/11/10

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例:
输入: s = “pwwkew”
输出: 3

重要思想方法:滑动窗口,遍历字符串,左端位置默认为0,右端也默认为0,从右端向右逐渐滑动。

class Solution {
public:
    //右端-左端+1即为字符串长度,散列表中的元素即为拆分的字符串
    //如果没有遇到散列表中的字符,则加入散列表并继续右移
    //如果遇到散列表中的字符,将原有字符删除并左端向右滑动,直到该元素不出现在散列表中为止
    //时间复杂度仅为O(n),比暴力解法的O(n^3)快得多!
    int lengthOfLongestSubstring(string s) {
        if(s.size()==0) return 0;
        int max_length = 0;
        int left = 0; //左端元素位置
        int right = 0; //右端元素位置
        unordered_set<char> lookup; //散列表,用于存放元素
        for(; right < s.size(); right++)
        {
            while(lookup.find(s[right])!=lookup.end()) //如果在散列表中发现右端元素
            {
                lookup.erase(s[left]); //删除左端元素
                left++; //左端向右一格
            }
            max_length = max(right-left+1, max_length); //比较和最大长度之间的关系
            lookup.insert(s[right]); //将右端元素插入散列表
        }
        return max_length;
    }
};

424. 替换后的最长重复字符 Medium 滑动窗口 2023/2/1

给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
在执行上述操作后,返回包含相同字母的最长子字符串的长度。
示例:输入:s = “AABABBA”, k = 1
输出:4
将中间的一个’A’替换为’B’,字符串变为 “AABBBBA”。
子串 “BBBB” 有最长重复字母, 答案为 4。

本题使用了freq数组,在使用滑动窗口的同时实时记录26个字母的频数,出现次数最多的字母数+更改次数如果小于窗口长,则说明左指针需要向右移动,直到满足条件。

class Solution {
public:
    int characterReplacement(string s, int k) {
        // 滑动窗口
        int left = 0;
        int right = 0;
        int res = 0;
        vector<int> freq(26, 0);
        while (right < s.size()) {
            freq[s[right] - 'A']++;
            // 求出最多字符的频数
            int max_freq = *max_element(freq.begin(), freq.end());
            // 当前窗口不行,左指针要右移
            while (right - left + 1 > k + max_freq) {
                freq[s[left] - 'A']--;
                left++;
            }
            res = max(res, right - left + 1);
            right++;
        }
        return res;
    }
};

438. 找到字符串中所有字母异位词 Medium 滑动窗口 2023/2/1

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

本题与上题类似,也是记录26个字母出现的频次,但本题更为简单,窗口定长,通过判断频次数组是否相等来判断是否是异位词。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        if (s.size() < p.size()) return vector<int>();
        // 滑动窗口
        // 统计滑动窗口中的字符数目
        vector<int> s_count(26);
        vector<int> p_count(26);
        vector<int> res;
        // 初始化
        for (int i = 0; i < p.size(); i++){
            s_count[s[i] - 'a']++;
            p_count[p[i] - 'a']++;
        }
        // 看看初始化后是否相同
        if (s_count == p_count) res.push_back(0);
        // 逐步删除i, 加入i + p.size()
        for (int i = 0; i < s.size() - p.size(); i++){
            s_count[s[i] - 'a']--;
            s_count[s[i + p.size()] - 'a']++;
            if (s_count == p_count) res.push_back(i + 1);
        }
        return res;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值