双指针or滑动窗口问题汇总~~~ 持续更新 2021.7.20

1838. 最高频元素的频数
题目:
  元素的 频数 是该元素在一个数组中出现的次数。
  给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。
思路:
  我发现这种花里胡哨难以确定选择规律的题目要么就是动态规划,要么就是基于排序后的一些操作。在这里我们想到,频数对应的元素最终还是来自数组中原有的元素,所以他问的是最大可能频数,这里的可能也是可以的意思。因为1 1 1 1 这样一个数组,让你操作4次,其实最大的频数是4,元素是2.但是我们可以选择忽略这样的操作。
  有了这样的想法就轻松了,我们先将数组排序,然后用双指针遍历数组
 在这里插入图片描述在这里插入图片描述
代码:

class Solution {
public:
    int maxFrequency(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        long long total = 0;
        int l = 0, res = 1;
        for (int r = 1; r < n; ++r) {
            total += (long long)(nums[r] - nums[r - 1]) * (r - l); //r-l
            while (total > k) {
                total -= nums[r] - nums[l];   //记录的就是以r为目标最大频数的结果会是多少
                ++l;  //看下图像就是这个把第一个指针的操作给他删去了 total<=k就行,差的步骤我给其他随便一个小喽喽消化
            }
            res = max(res, r - l + 1); //当r=0的时候本身res=1了
        }
        return res;
    }
};

————————————————————————————————————————  
1877. 数组中最大数对和的最小值
题目: 在长度为偶数得数组中可以进行分组形成数组对,找出并返回最小的 最大数组对

思路: 排序+贪心原则,最小得最大数组对一定是最小元素和最大元素组合,因为最大元素迟早要处理,如果这时候他其实不是最大的,那就缩进。

class Solution {
public:
    int minPairSum(vector<int>& nums) {
        int res=0;
        sort(nums.begin(),nums.end());
        int n=nums.size();
        for(int i=0; i<n/2;i++){
            res=max(res,nums[i]+nums[n-1-i]);
        }
        return res;    
    }
};
class Solution {
public:
    int minPairSum(vector<int>& nums) {
        int res=0;
        sort(nums.begin(),nums.end());
        int n=nums.size();
        for(int i=0,j=n-1;i<j;i++,j--){
           // if(nums[i]+nums[j]>res)
           //     res=nums[i]+nums[j];
           res=max(res,nums[i]+nums[j]);
        }
        return res;    
    }
};

下面引入滑动窗口的题目
—————————————————————————————————————————————
76.最小覆盖子串–典型滑动窗口~~,详情看Labuladong

题目:
  给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
思路:
  用滑动窗口,右边界找到1个可行解,记录长度,然后收缩左边界,不断记录最小长度,直到不能收缩。此时继续扩大右边界,寻找可行解。同上面操作~

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    // 记录最小覆盖子串的起始索引及长度
    int start = 0, len = INT_MAX;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        while (valid == need.size()) {
            // 在这里更新最小覆盖子串
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d]) //这一步不能少,因为后续继续搜索比如ABCA又遇到A了windows还是会++/不过value不会加了!巧妙之处是正好
                    valid--;
                window[d]--;
            }                    
        }
    }
    // 返回最小覆盖子串
    return len == INT_MAX ?
        "" : s.substr(start, len);

    }
};

567. 字符串的排列
题目: 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。换句话说,第一个字符串的排列之一是第二个字符串的 子串 。
思路:
  这种题目,是明显的滑动窗口算法,相当给你一个S和一个T,请问你S中是否存在一个子串,包含T中所有字符且不包含其他字符?
  对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变两个地方:
  1、本题移动left缩小窗口的时机是窗口大小大于t.size()时,因为排列嘛,显然长度应该是一样的。
  2、当发现valid == need.size()时,就说明窗口中就是一个合法的排列,所以立即返回true。
  至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
         unordered_map<char, int> need, window;
    for (char c : s1) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while (right < s2.size()) {
        char c = s2[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        while (right - left == s1.size()) {//labuladong是>=我这边是==
            // 在这里判断是否找到了合法的子串
            if (valid == need.size())
                return true;
            char d = s2[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    // 未找到符合条件的子串
    return false;
    }
};

—————————————————————————————————————————————
438. 找到字符串中所有字母异位词
题目: 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指字母相同,但排列不同的字符串。注意到排列相同也是异位词了
思路: 跟寻找字符串的排列一样,只是找到一个合法异位词(排列)之后将起始索引加入res即可。
代码:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        unordered_map<char, int> need, window;
        for (char c : p) need[c]++;
        int left = 0, right = 0;
        int valid = 0;
        vector<int>res;
        while (right < s.size()) {
            char c = s[right];
            right++;
        // 进行窗口内数据的一系列更新
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c])
                    valid++;
        }

        // 判断左侧窗口是否要收缩
        while (right - left == p.size()) {
            // 在这里判断是否找到了合法的子串
            if (valid == need.size())
                res.push_back(left);
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    return res;                              
    }
};

3.无重复字符的最长子串
题目: 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
思路: 这个题终于有了点新意,不是一套框架就出答案,不过反而更简单了,稍微改一改框架就行了:
在这里插入图片描述

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> window;
        int left = 0, right = 0;
        int res = 0; // 记录结果
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        window[c]++;
        // 判断左侧窗口是否要收缩
        while (window[c] > 1) {
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            window[d]--;
        }
        // 在这里更新答案
        res = max(res, right - left);
    }
    return res;
    }
};

综上:
滑动窗口的基础模板如下

string minWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    // 记录最小覆盖子串的起始索引及长度
    int start = 0, len = INT_MAX;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        while (valid == need.size()) {
            // 在这里更新最小覆盖子串
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }                    
        }
    }
    // 返回最小覆盖子串
    return len == INT_MAX ?
        "" : s.substr(start, len);
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值