HOT100打卡—day4—【子串】—23.8.5,8.8

 1 560. 和为 K 的子数组不能用滑动窗口!

思路:

(1)初步思路:滑动窗口,两个指针 l,r 维护一个试探序列 当右边新加入的数,正好使得序列和为k,ans++,sum = 0 ,l++,r不变。如果新加入的数太大,就开始左端点收缩,直到 序列和 <= k,如果新加如的数太小就继续往后加。但是这个思路的bug——但是过不了[-1,-1,1]之类带负数的。因为,数组元素是既有正数又有负数的,且要求是连续的子数组。数组元素既有正数又有负数,即数组不是单调的。因此不适合用滑动窗口的方法。**滑窗特点是,固定了左边界以后,右边界找到了一个可行解以后,右边界再靠右边的更大的区间肯定不存在目标值,所以才将左边界继续向右滑。**在这道题里显然没有这个性质。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        // 连续性质——滑动窗口? 一个和小于等于k的窗口 当新加入一个数
        // 如果大于左边出 出到窗口sum小于等于k 
        int l = 0;
        int r = 0;
        int sum = nums[0];
        int ans = 0;
        if(sum == k)ans++;
        for(int i = 1; i < nums.size();i++)
        {
            int tmp = sum + nums[i];
            if(tmp < k)
            {
                r++;
                sum = tmp;
            }
            else if(tmp == k)
            {
                ans++;
                r++;
                sum = tmp;
            }
            else 
            {
                r++;
                sum = tmp;
                while(sum > k && l <= r && r < nums.size())
                {
                    sum -= nums[l];
                    l++;
                }
                if(sum == k)ans++;
            }
        }
        return ans;
    }
};

(2)其实是前缀和的板子题 但没有tag,我就完全忘了前缀和这件事。这种子区间和需要条件反射想到前缀和。

尝试只用前缀和:但是TLE,1e4的数据范围不能能O(n^2)的算法 1e3的才可以用。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {

        vector<int> qian(nums.size()+10);

        qian[0] = nums[0];
        for(int i = 1; i < nums.size();i++)
            qian[i] = qian[i-1] + nums[i];
        
        int ans = 0;
        for(int i = 0; i < nums.size();i++) //左端点
        {
            for(int j = i; j < nums.size();j++)// 右端点
            {
                int tmp = qian[j] - qian[i] + nums[i];
                if(tmp == k)ans++;
            }
        }
        return ans;
    }
};

优化:hash表

(学习了别人的题解)其实我们不关心具体是哪两项的前缀和之差等于k,只关心等于 k 的前缀和之差出现的次数c,就知道了有c个子数组求和等于k。
1构造前缀和:遍历 nums 数组,求每一项的前缀和,统计对应的出现次数,以键值对存入 map。
2核心遍历:边存边查看 map,如果 map 中存在 key 为「当前前缀和 - k」,说明这个之前出现的前缀和,满足「当前前缀和 - 该前缀和 == k」,它出现的次数,累加给 count。

link:笨猪爆破组

如果先把map存好遍历map有一些情况要特殊考虑不好写。我也不知道为啥不好写 错在哪 反正很多错

先加入mma[0] = 1; // 原因是:如果某段前缀和prefix_sum(i)为 k,则prefix_sum - k == 0,所以需要提前往哈希表中存入前缀和0,其频数初始化为1。即这里因为某个子区间和直接用map[j]-map[i] 没有加上num[i] 所以需要考虑上 i = 0的情况。即只需要map[j] 就能等于k的情况,即需要加上mma[0]=1。

AC代码:

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {

        map<int,int> mma;  // key存每一项前缀和值 val是次数

        mma[0] = 1; // 原因是:如果某段前缀和prefix_sum(i)为 k,则prefix_sum - k == 0,所以需要提前往哈希表中存入前缀和0,其频数初始化为1
        
        int ans = 0;
        int sum = 0;
        // 遍历map 不好写
        for(auto num : nums)
        {
            sum += num;
            if(mma.count(sum - k))ans+= mma[sum-k];
            mma[sum]++;
        }
        return ans;
    }
};

2 239. 滑动窗口最大值

239. 滑动窗口最大值

之前做过,这次二刷想到了左边界存子序列最大值的队列,但是每次左边最大退出时候,找新的最大时候要重新遍历一次队列,TLE了,看了题解忘了这题要用单调队列,即保持队列中是单调减小的。写的时候经常有时候糊里糊涂了。脑子跟不上我写的front,还是back的逻辑。

AC代码:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // 一个思路: 维护一个窗口左边界是当前小于等于k长度的子序列的最大值 优化==》 单调队列

        vector<int> ans;

        deque<int> q;  // 存下标
        for(int t = 0; t <= k-1 ; t++)
        {
            if(q.empty())q.push_back(t);
            else{
                while(!q.empty() && nums[t] > nums[q.back()])q.pop_back();
                q.push_back(t);
            }
        }

        ans.push_back(nums[q.front()]);

        for(int j = k; j< nums.size();j++)
        {

            while(!q.empty() && nums[j] > nums[q.back()])q.pop_back();
            q.push_back(j);
            
            if((j - q.front()+1) > k)q.pop_front();
            
            ans.push_back(nums[q.front()]);
        }
        return ans;
    }
};

3 76. 最小覆盖子串

76. 最小覆盖子串

有点难。

初始思路:

        // 典型的滑动窗口思路,窗口扩展时寻找可行解,窗口收缩时优化可行解

        // 思路:一个unordered_map装着t的key和对应val

        // s采用滑动窗口策略 当新加入一个 对应map-key的val--,num++

        // 当map里面没有val是大于0的key的时候,即,找到了一个合适的答案

        // 左边回收 收到最小还能满足 map里面没有val是大于0的key

        // 更新长度和当前最优串!

        // 然后 左边界i++,对应val++ 然后重复右边前进

但是有个问题就是 经常要找map里面没有val是大于0的key,那时间复杂度可能就是O((m+n)*52)。

这样写的代码:

思路正确,但是过不了第265个点

代码:

class Solution {
public:
    string minWindow(string s, string t) 
    {
        // 典型的滑动窗口思路,窗口扩展时寻找可行解,窗口收缩时优化可行解
        // 思路:一个unordered_map装着t的key和对应val
        // s采用滑动窗口策略 当新加入一个 对应map-key的val--
        // 当map里面没有val是大于0的key的时候,即,找到了一个合适的答案 
        // 左边回收 收到最小还能满足 map里面没有val是大于0的key
        // 更新长度和当前最优串!
        // 然后 左边界i++,对应val++ 然后重复右边前进 

        unordered_map<char,int> need;
        int cnt = 0;
        for(char u : t)
        {
            need[u]++;
            cnt++;
        }

        string bestans;
        int bestnum = s.size()+100;
    
        deque<char> now;
        int okans = 0;

        for(int r = 0; r < s.size();r++)  //扩大右边界
        {
            now.push_back(s[r]);
            if(need.count(s[r]))
            {
                if(need[s[r]] > 0)cnt--;
                need[s[r]]--;
            }

            // 判断一下找齐没有
            int ok;
            if(cnt == 0)ok = 1;
            else ok = 0;
            
            if(!ok)continue;
            else
            {
                okans = 1;
                char tmp;
                // 进到这里一开始ok都是1即cnt=0
                while(ok)
                {
                    tmp = now.front();
                    now.pop_front();
                    if(need.count(tmp))
                    {
                        need[tmp]++;
                        if(need[tmp] > 0)cnt++;
                    }
                    if(cnt == 0)ok = 1;
                    else ok = 0;
                }
                now.push_front(tmp);  //复原

                // 更新
                int num = 0;
                string tmp1 = "";
                for(auto y:now)
                {
                    num++;
                    tmp1 += y;
                }
                cout << tmp1 << endl;

                if(num < bestnum)
                {
                    bestnum = num;
                    bestans = tmp1;
                }
                now.pop_front();
            }
        }
        if(!okans)return "";
        else return bestans;
    }
};





 总结:

1. 连续子序列之和要条件发射==> 前缀和。 

2. 哈希优化。

3. 单调队列

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值