[C/C++]滑动窗口专题/最小覆盖子串/字符串排列/找到字符串中所有字母异位词/无重复字符的最长子串/串联所有单词的子串

以下内容节选自公众号:labuladong《我写了套框架,把滑动窗口算法变成了默写题》,传送门在最下方参考链接1。
我觉得非常牛逼,在此记录,方便复习。
1、框架
2、LeetCode 76:最小覆盖子串
3、LeetCode 567:字符串排列
4、LeetCode 438:找到字符串中所有字母异位词
5、LeetCode 3:无重复字符的最长子串
6、LeetCode 30: 串联所有单词的子串

1、框架

以下框架中,遇到相关问题可改动三个地方:
(1)debug处可以删除。
(2)两处...表示更新窗口数据的地方,它们的操作是完全对称的。

/* 滑动窗口算法框架 */
void slidingWindow(string  s, string t)  //source , target
{
	unordered_map<char, int> need, window;  //目标集,窗口集
	for (auto c : t)   //计算目标集
		need[c]++;
	int left = 0, right = 0, valid = 0;  //valid用于指示当前窗口状态是否匹配目标集
	while (right < s.length())  //计算的区间,左闭右开[left, right)
	{
		//c 是将移入窗口的字符
		char c = s[right];
		//移动右边界
		right++;
		//进行窗口内的数据的更新
		...

		/* debug输出位置*/
		printf("window:[%d, %d)\n", left, right);

		//判断窗口左侧是否需要收缩
		while (window needs shrink)
		{
			//d 是将要移出窗口的字符
			char d = s[left];
			//移动左边界
			left++;
			//进行窗口内的数据的更新
			...
		}
	}
}

2、LeetCode 76:最小覆盖子串

给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:
输入:S = “ADOBECODEBANC”, T = “ABC”
输出:“BANC”

提示:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。


思路:

  1. 算法要求O(n) 的时间复杂度,即只遍历一次字符串。
  2. 套用上面的框架,计算最短的窗口即可。

代码:

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char,int> need,window;  //目标集,窗口集
        int left=0,right=0,valid=0;
        int start=0,min_len=INT_MAX;   //存储结果
        for(auto c:t)
            need[c]++;
        while(right<s.length())
        {
            char c=s[right++];    //移动右边界
            if(need.count(c)>0)   //记录窗口集
            {
                window[c]++;
                if(window[c]==need[c])
                    valid++;
            }
            while(valid==need.size())  //窗口包含目标集,则进行优化
            {
                if(right-left<min_len)  //更新结果
                {
                    start=left;
                    min_len=right-left;
                }
                char d=s[left++];  //移动左边界
                if(need.count(d)>0)  //更新窗口集
                {
                    if(window[d]==need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        return min_len==INT_MAX?"":s.substr(start,min_len);            
    }
};

结果:
在这里插入图片描述

3、LeetCode 567:字符串排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。

示例1:
输入: s1 = “ab” s2 = “eidbaooo”
输出: True
解释: s2 包含 s1 的排列之一 (“ba”).

示例2:
输入: s1= “ab” s2 = “eidboaoo”
输出: False

注意:
输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间


代码:

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

        int left=0,right=0,valid=0;        
        while(right<s2.length())
        {
            char c=s2[right++];
            if(need.count(c)>0)
            {
                window[c]++;
                if(window[c]==need[c])
                    valid++;
            }
            while(right-left==s1.length())
            {
                if(valid==need.size())
                {
                    return true;
                }
                char d=s2[left++];               
                if(need.count(d)>0)
                {
                    if(window[d]==need[d])
                        valid--;
                    window[d]--;
                    
                }
            }
        }
        return false;
    }
};

结果:
在这里插入图片描述

4、LeetCode 438:找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。

示例 1:
输入:
s: “cbaebabacd” p: “abc”
输出:
[0, 6]

解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。

示例 2:
输入:
s: “abab” p: “ab”
输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。


代码:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {        
        unordered_map<char, int> need,window;
        for(auto c:p)
            need[c]++;

        int left=0,right=0,valid=0;
        vector<int> res;       
        while(right<s.length())
        {
            char c=s[right++];
            if(need.count(c))
            {
                window[c]++;
                if(window[c]==need[c])
                    valid++;                
            }
            while(right-left==p.length())
            {
                if(valid==need.size())
                {
                    res.push_back(left);
                }
                char d=s[left++];                
                if(need.count(d))
                {
                    if(window[d]==need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        return res;
    }
};

5、LeetCode 3:无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。


代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> window;
        int left=0,right=0,max_len=0;
        char c;
        while(right<s.length())
        {
            c=s[right++];
            window[c]++;
            
            while(window[c]>1)
            {
                char d=s[left++];
                window[d]--;
            }
            max_len=max(max_len,right-left);
        }
        return max_len;
    }
};

6、LeetCode 30: 串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]


思路:
这道题的又不一样了,可以看到之前窗口边界 left 和 right 是以步长1增加,这次是以字典中的单词长度 m 为步长 增加。因此,我们可以在 [0,m)设置循环,作为起始点使用前面的滑动窗口框架。
另外注意,窗口集在每次循环中,要清空,去除上次循环的遗留信息。

代码:

class Solution {
public:      
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;              
        int n=words.size();
        if(n==0)               //判断空集,以LeetCode的习性,测试用例中大多数会有空集
             return res;
        int m=words[0].size();
        if(s.length()<n*m)
            return res;
         
         //框架开始   
		unordered_map<string,int> need,window; 
        for(int i=0;i<n;i++)
            need[words[i]]++;
        //循环,遍历起始位置
        for(int i=0;i<m;i++)
        {
            window.clear();   //重要,清空窗口集
            int left=i,right=i,valid=0; 
            while(right<s.length())
            {
                string c=s.substr(right,m);
                right+=m;
                if(need.count(c))
                {
                    window[c]++;
                    if(window[c]==need[c])
                        valid++;
                }
                while(right-left==n*m)
                {
                    if(valid==need.size())
                        res.push_back(left);
                    string d=s.substr(left,m);
                    left+=m;
                    if(need.count(d))
                    {
                        if(window[d]==need[d])
                            valid--;
                        window[d]--;
                    }                
                }
            }
        }                            
        return res;
    }
};

结果:
在这里插入图片描述

参考链接:
[1] labuladong:我写了套框架,把滑动窗口算法变成了默写题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值