滑动窗口(8题)

目录

1.长度最小的子数组

2.无重读字符的最长字串

3. 最大连续1的个数

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

5.水果成篮

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

7.串联所有单词的子集

8.最小覆盖字串


1.长度最小的子数组

. - 力扣(LeetCode)

对于从同一端往另一端移动的双指针,我们可以采用滑动窗口的策略解决。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int ret = INT_MAX;
        int left = 0;
        int sum = 0;
        int right =0;
        for(left = 0, right = 0; right < nums.size(); right++)
        {
            sum += nums[right];//进窗口
            while(sum >= target)//判断
            {
                ret = min(ret,right - left + 1);
                sum -= nums[left];
                left++;
            }
        }
        return ret == INT_MAX ? 0 : ret;
        return ret;

    }
};

        如例子 2 3 1 2 4 3

        我们让left和right从下标为0处出发,让right不断右移,一直让left到right这段的子数组之和大于等于目标数。

        当left到right这段的子数组之和大于等于目标数时,再向后移动时,子数组的长度会更长,因此我们通过将left右移,判断能否减去一些较小的数,使范围内的子数组长度减小,但是仍然大于目标数。若可以,将每次的结果和ret比较

        比如 1 2 3 4 50   ,目标数为40.

        直到子数组小于目标数了,在让right右移。

        但也有可能,整个数组加起来都达不到目标数的大小,我们可以事先遍历判断,也可以判断ret是否被赋值过,来进行返回

2.无重读字符的最长字串

. - 力扣(LeetCode)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int hash[128] ={0};
        int size = s.size();
        int left = 0;
        int right = 0;
        int ret = 0;
        for(right; right < size; right++)
        {
            hash[s[right]]++;
            while(hash[s[right]] >1)
            {
                hash[s[left]]--;
                left++;
            }
             ret = max(ret,right-left + 1);
        }
        return ret;
    }
};

3. 最大连续1的个数

 . - 力扣(LeetCode)

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int ret = 0;
        int sum = 0;
        int size =nums.size();
        int left = 0;
        int right = 0;
        for(right; right < size; right++)
        {
            if(nums[right] == 0)sum++;
            while(sum > k)
            {
                if(nums[left] == 0)sum--;
                left++;
            }
            ret = max(ret,right - left + 1);
        }
        return ret;
    }
};

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

. - 力扣(LeetCode)

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
    int target = 0;
    int size = nums.size();
    for(int i = 0 ;i < size; i++)
    {
        target += nums[i];
    }
    target -= x;
    if(target < 0)return -1;//如果数组总和还没有x大,那就不能把x减到0,直接返回-1
    int left = 0;
    int right = 0;
    int count = 0;
    int ret = -1;
    for(;right < size; right++)
    {
        count += nums[right];
        while(count > target)
        {
            count -= nums[left];
            left++;
        }
        if(count == target)(ret = max(ret,right-left + 1));
    }
    return ret == -1 ? ret : size - ret;
    }
};

        这个题目直接正面做不好做,我们可以转化为子数组中和为x的最长长度

        这样即可用滑动窗口做题 

5.水果成篮

. - 力扣(LeetCode)

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        vector<int>arr(n,0);
        int left = 0;
        int right = 0;
        int count = 0;
        int ret = 0;
        for(;right < n;right++)
        {
            arr[fruits[right]]++;
            if( arr[fruits[right]] == 1)count++;
            while(count > 2)
            {
                 arr[fruits[left]]--;
                 if( arr[fruits[left]] == 0)count--;
                 left++;
            }
            if(count >= 1)ret = max(ret,right-left+1);
        }
        return ret;
    }
};

        这里我们引进一个计数器, 用于判断是否需要出窗口。

        仅有新水果第一次入窗口,count才会增加。

        当count > 2 时出窗口。

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

 . - 力扣(LeetCode)

         首先我们还是先存一个哈希表,用一个数组简单表示就可以了。

        hash1表示目标字符串中的字符情况,hash2表示left到right字符串中的字符情况

#define N 128
class Solution {
public:
    bool judge(int* hash1,int* hash2)
    {
        for(int i = 0; i < N; i++)
        {
            if(hash1[i] != hash2[i])return false;
        }
        return true;
    }
    vector<int> findAnagrams(string s, string p) 
    {
        int size = p.size();
        int hash1[N] = {0};
        for(int i = 0; i < size; i++)
        {
            hash1[p[i]]++;
        }
        int hash2[N] = {0};
        vector<int> ret;
        int left = 0;
        int right = 0;
        for(right;right < s.size(); right++)
        {
            hash2[s[right]]++;
            while(right- left +1 > size)//这里其其实用if更合适,因为窗口的长度固定,达到长度之 
                                        //后,right移动一次,left移动一次,最多只会超过一个
            {
                hash2[s[left]]--;
                left++;
            }
            if(judge(hash1,hash2))ret.push_back(left);
        }
        return ret;
    }
};

         第一个代码的思路和前面一样,先入窗口,判断长度不能超过目标字符串长度,进行出窗口,然后再判断是否符合条件。

        我们这里写一个判断函数,来判断hashi1和hash2一样不一样。

class Solution {
public:
    vector<int> findAnagrams(string s, string p) 
    {
        int size = p.size();
        int hash1[128] = {0};
        for(int i = 0; i < size; i++)
        {
            hash1[p[i]]++;
        }
        int hash2[128] = {0};
        int count = 0;
        vector<int> ret;
        int left = 0;
        int right = 0;
        for(right;right < s.size(); right++)
        {
            hash2[s[right]]++;
            if(hash2[s[right]]<= hash1[s[right]])count++;
            if(right-left+1 > size)
            {
                hash2[s[left]]--;
                if(hash2[s[left]] < hash1[s[left]])count--;
                left++;
            }
            if(count == size)ret.push_back(left);
        }
        return ret;
    }
};

        第二个代码时间复杂度要低很多,这里我们维护一个count,来计算有效字符的个数。

        我们进行入窗口操作时,我们先入窗口,然后判断,当hash2中对应位置 <= hash1中对应位置时,该字符有效,也就是我们还需要这个字符,比如我们需要两个a,当入窗口后a的个数为1,或者a的个数为2,我们都是需要的,此时count + 1。

        当字符串里面的字符超过我们需要的值时,对该字符的计数增加,但是有效字符数不增加,这时候我们就可能遇到right-left+1 > 目标字符串长度  ,这时候我们不断出窗口,直到符合要求,这时候我们也同时判断有效字符是否减小。

7.串联所有单词的子集

. - 力扣(LeetCode)

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        int len = words[0].size();
        int size = words.size();
        unordered_map<string,int> hash1;//储存words的hash1
        for(auto ch:words)
        {
            hash1[ch]++;
        }
        vector<int> ret;
        for(int i = 0; i < len; i++)
        {
            int count = 0;
            int left =i;
            int right = i;
            unordered_map<string,int> hash2;//储存遍历的hash2
            for(;right <s.size(); right+= len)
            {
                //入窗口,substr(right,right+len) 
                string in = s.substr(right,len);
                hash2[in]++;
                if(hash1[in] && hash2[in] <= hash1[in])count++;
                while(right - left + 1  > len * size)
                {
                    string out = s.substr(left,len);
                    hash2[out]--;
                    if(hash1[out] && hash2[out] < hash1[out])count--;
                    left+= len;
                }
                if(count == size)ret.push_back(left);
            }
        }
       return ret;
    }
};

        由于words中所有单词都是等长的,因此我们可以将一个字符串看为一组,以字符串的长度len为窗口一次滑动的距离,即单位长度 。

        然后定义一个哈希表,思路同6

8.最小覆盖字串

. - 力扣(LeetCode)

class Solution {
public:
    string minWindow(string s, string t) {
        int hash1[128]={0};
        int hash2[128] ={0};
        int kinds = 0;
        int count = 0;
        int minlen = INT_MAX;
        int begin = -1;
        for(auto ch:t)
        {
            hash1[ch]++;
            if(hash1[ch] == 1)kinds++;
        }
        for(int left = 0,right = 0,count = 0;right < s.size();right++)
        {
            int in = s[right];
            hash2[in]++;
            if(hash2[in] == hash1[in])count++;
            while(count == kinds)
            {
               if(right - left + 1 < minlen)
               {
                minlen = right-left+1;
                begin = left;
               }
               int out = s[left];
               hash2[out]--;
               if(hash2[out] < hash1[out])count--;
               left++;
            }
        }
        if(begin == -1)return "";
        else return s.substr(begin,minlen);
    }
};

         定义两个哈希表

        我们这里采用kinds变量来储存遍历时需要进行判断的种类数。利用count计数判断

        当hash1与hash2中同位置的值相等,count++。即有一个字符满足了题目要求。

        这题要求我们取最小长度,所以我们right一直向右移动直到left到right包含所有的目标字符,然后出窗口,查看是否有更优解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值