LeetCodes刷题总结4——最小覆盖子串

题目

76. 最小覆盖子串 - 力扣(LeetCode) (leetcode-cn.com)

思路

这道题非常经典,属于滑动窗口的经典思想。

首先看到这题估计很多人会去想动态规划,看当前子串与字符串t的关系,但是你会想动态规划方程怎么写?可能几乎没有出路。

好吧,任何算法都是暴力求解优化来的,我们先想暴力算法,把字符串s的所有子串都拿出来去看有没有覆盖字符串t,一定可行但是想都不要想一定超时,那么我们考虑剪枝,这个子串一定大于等于t,不然不可能覆盖(注:题目要求结果覆盖t的所有字符串,包括重复的)。这样我们考虑滑动窗口思想。

顾名思义,滑动窗口在字符串s上定义两个左右边界,不断移动左边界或右边界寻找答案。在此之前我们先考虑一个问题,假设你现在确定好了边界,怎么判断这个子串覆盖了t没?估计你会想对于这个子串一个个遍历,找个变量记录覆盖t的字符数。但是这样做又陷入了高时间复杂度的循环中。我们考虑用map来记录当前子串各字符个数和t的各字符个数,在移动边界之前先把t的map填好,同时保留记录覆盖t的字符数的变量(我设为num)。这样做不管子串怎样变我们只要对子串的map进行增删不就可以了吗?

然后再看移动边界的策略。我们首先初始化左右边界(left=0,right=-1,-1是为了便于循环),然后先移动右边界,在移动的过程中不断增加子串map的内容,同时记录num,当num=t.length()时,说明一个结果出来了,此时先移动左边界剔除左边多余的值,再将结果暂存。然后此时左边界再向右移动,num减一,再去移动右边界重复上面的过程。

当然上面的文字一看就迷乱了,可能不知道我在说什么。这是就拿出纸笔或者电脑画板,找个简单的例子,边画边想边敲代码。我认为这是一个非常好的做法,因为很多细节只有自己慢慢推敲才不容易乱。

 上面是我随手画的图,其实一点也不精致,但是非常便于自己理清思路。

代码

我的代码思路并不新,复杂度也不占优,细节都在注释里面了,仅供参考。

#include<iostream>
#include<string>
#include<unordered_map>

using namespace std;

class Solution {
public:
    string minWindow(string s, string t) {
        int s_len=s.length();
        int t_len=t.length();
        string res="";
        //判断临界条件
        if(s_len==0||t_len==0||s_len<t_len){
            return res;
        }
        //这两个map分别记录s和t的各元素个数,便于对比
        unordered_map<char,int> s_map,t_map;
        //先把t各元素个数记录下来
        for(char c:t){
            t_map[c]++;
        }
        int l=0;
        int r=-1; //滑动窗口的左右边界
        string temp; //s的子串暂存
        int num=0; //temp覆盖t的字符个数
        int len=INT_MAX;
        //正常右边界向前
        while(r<s_len){
            r++;
            if(s_map[s[r]]<t_map[s[r]]) num++;
            s_map[s[r]]++;
            if(num==t_len) { //已经构成一个答案了,记录
                //先把左边界向右滑动,剔除左边多余的
                while(s_map[s[l]]!=t_map[s[l]]){
                        s_map[s[l]]--;
                        l++;
                }
                //记录结果
                temp=s.substr(l,r-l+1);
                if(temp.length()<len){
                    res=temp;
                    len=temp.length();
                }
                //丢掉当前左边界字符,回到外层while移动右边界继续寻找
                s_map[s[l]]--;
                l++;
                num--;//这一步很关键,因为上面内层while已经剔除多余了
            }
        }
        return res;
    }
};

int main(){
    string s,t;
    cin>>s;
    cin>>t;
    string res;
    res=Solution().minWindow(s,t);
    cout<<res<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值