11.7滑动窗口

11/7

每日一题:长度为k的子数组的能量值2

题目跟昨天是一样的,只是复杂度要求不同,昨天的做法依旧能过

class Solution {
public:
    vector<int> resultsArray(vector<int>& nums, int k) {
      int n = nums.size();
       vector<int> res(n - k + 1 , -1);
       int cnt = 0;
       for (int i = 0; i < n; i++) {
          if(i == 0 || nums[i] - nums[i - 1] == 1) cnt ++;
          else cnt = 1;

          if(cnt >= k){
            res[i - k + 1] = nums[i];                       
          }
       }
       return res;
    }
};

209.长度最小的子数组

NOT AC

思路:时间复杂度O(n)的做法:滑动窗口

常规方法每次确定子数组的开始下标,然后得到长度最小的子数组,因此时间复杂度较高。为了降低时间复杂度,可以使用滑动窗口的方法。

定义两个指针 start end分别表示子数组(滑动窗口窗口)的开始位置和结束位置,维护变量 sum 存储子数组中的元素和(即从 nums[start]nums[end] 的元素和)。

每一轮迭代,将 nums[end] 加到 sum,如果 sum≥s,则更新子数组的最小长度(此时子数组的长度是 end−start+1),然后将 nums[start]sum 中减去并将 start 右移,直到 sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将end右移。

复杂度解释:

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作1次,出去操作1次,每个元素都是被操作两次,所以时间复杂度是 2 * n 也就是O(n)。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int start = 0;
        int sum = 0;
        int res = 0x3f3f3f3f;
        for (int end = 0 ; end < n ; end ++){
            sum += nums[end];
            while(sum >= target){
              res = min(res , end - start + 1);
              sum -= nums[start ++];
            }
        }
        return res == 0x3f3f3f3f? 0 : res;
    }
};

M:904.水果成篮

思路:无序哈希表+滑动窗口

同类的水果要把数量合并,因此考虑用无序图哈希表,注意定义方法是

unordered_map<int , int> mp;
//加fruits[end]类型的水果数量
mp[fruits[end]] ++;

当哈希表size>2时,用迭代器It找到窗口最左边的值fruits[start],将它的数量减一,直到减至0就删除(因为fruits数组可能出现 [ 1 , 2 , 1 , 2 ,1 ,2···]的波动数列),每轮将窗口起始位置右移。在满足mp.size() <= 2的每个循环末尾更新res为最大值。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
       unordered_map<int , int> mp;
        int res = 0;
        int start = 0;      
        for (int end = 0 ; end < fruits.size() ; end ++){
            mp[fruits[end]] ++;
            while(mp.size() > 2){
              auto it = mp.find(fruits[start]);
              -- it->second;
              if(it->second == 0) mp.erase(it);
              start ++;
            }
            res = max(res , end - start + 1);    
        }
        return res;
     }  
};

H:76.最小覆盖子串(opens new window)

思路:哈希表+滑动窗口

定义两个哈希表,ss存放滑动窗口中元素出现次数,tt存放t中元素出现次数,tt可直接根据t初始化。

定义一个检查当前窗口是否合格的函数check(),要求tt中元素出现的次数小于等于ss中元素出现次数。

bool check(){
	for (auto it : tt){
  if(ss[it.first] < it.second)  return false; 
}
return true;
}

边界条件:

  • 如果s.size() < t.size() , 直接输出空串“”;

  • 如果每次r指针右移更新进来的都是没用的字母,即在tt里找不到,最后也是输出空串。如样例

    s = "a" , t = "b"
    

    因此需要在每次r指针右移后加入判断条件,判断加进来的元素是否在tt里面出现过,出现过了再以check()判断子串是否合格,并更新子数组长度len,以及答案的左边界ans_l,如果从始至终没有更新过ans_l(如果更新则至少为l的初值0),则说明输入的都是没用的字母。

    for(int r = 0 ; r < s.size() ; r ++){
      ss[s[r]] ++;
      if(tt.find(s[r]) != tt.end()){ //tt里有s[r],更新有效
        while(check() && l <= r){ //满足题意,则更新子数组边界和长度
          if(len > r - l + 1){
            len = r - l + 1;
            ans_l = l
          }
          ss[s[l]] --;//如果当前子串合格,则尝试右移左边界。
          l ++;
        }
      }
    }
    

最后输出时可以直接用substr函数截取返回,也是为什么计算ans_l,答案长度len的原因

return s.substr(ans_l , len);
class Solution {
public:
     unordered_map<char , int> ss;
     unordered_map<char , int> tt;

    bool check(){
      for (auto it: tt){
        if(ss[it.first] < it.second)  return false;
      }
      return true;
    }

    string minWindow(string s, string t) {
      
        if(s.size() < t.size() )  return "";
        for (char ch : t) tt[ch] ++;

        int l = 0;
        int ans_l = -1;
        int len = 0x3f3f3f3f;       
        for (int r = 0 ; r < s.size() ; r ++){
          ss[s[r]] ++;
          if(tt.find(s[r]) != tt.end()){
                while(check() && l <= r){
                    if(len > r - l + 1){
                    len = r - l + 1;
                    ans_l = l;
                    }
                    auto it = ss.find(s[l]);
                    -- it->second;
                    if(it->second == 0)  ss.erase(it);
                    l ++;
                }
          }
        }
        if(ans_l == -1)  return "";
        return s.substr(ans_l , len);
    }
};

11.盛最多水的容器

思路:双指针

定义左右指针l = 0 , r = n - 1,由于每次都是移一位,让高度小的移动能使value尽可能大。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int res = 0;
        int value = 0; 
        for ( int l = 0, r = n - 1 ; l < r;  ){
            value = (r - l) * min(height[l] , height[r]);
            if(value > res)  res = value;  
            if(height[l] > height[r]) r --;
            else l ++;                 
        }
        return res;
    }
};

3.无重复字符的最长子串

本题与76.最小覆盖子串类似的做法。区别是本题求最大子串长度,因此在不满足check()时才右移左边界,在满足时可以一直右移右边界。

先定义check()函数,字母个数大于1的时候返回false

每次窗口右边界右移进来新元素的时候check(),满足则更新答案res;当不满足的时候试着把左边界右移,直到符合为止。

class Solution {
public:
    unordered_map<char , int> ss;
    bool check(){
      for (auto it : ss){
        if(it.second > 1)  return false;
      }
      return true;
    }

    int lengthOfLongestSubstring(string s) {       
        int l = 0;
        int res = 0;
        for (int r = 0 ; r < s.size() ; r ++){
            ss[s[r]] ++;
            if(check() && l <= r){
              res = max(res , r - l + 1);   
            }
            while(!check()){
                ss[s[l]] --;
                l ++;               
            }       
          }
          return res;
        }
};
posted @ 2024-11-07 19:53  七龙猪  阅读(2)  评论(0)    收藏  举报
-->