leetcode 第 76 题:最小覆盖子串(C++)
创始人
2024-06-02 20:13:31
0

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

和这个:LeetCode第 3 题:无重复字符的最长子串(C++)_qq_32523711的博客-CSDN博客有点相似

如果使用暴力解法

for (int i = 0; i < s.size(); i++) for (int j = i + 1; j < s.size(); j++)  //i, j相当于窗口左右边界if s[i:j] 完全覆盖 t更新窗口长度(并记录边界)

至少是O(n^2)的时间复杂度(其实还需要对覆盖与否进行判断等等)

这种类型题目一般是用双指针法(滑动窗口)

对了,下面这个测试样本好坑,意味着不能使用set了

输入:
"aa"
"aa"
预期结果:
"aa"

参考位哥们的题解蛮有意思,说的简单易懂,思路很清晰:

把滑动窗口算法变成了默写题 - 力扣(LeetCode)

滑动窗口的算法逻辑一般为:

int left = 0, right = 0; // 初始化左右边界while (right < s.size()) {`// 增大窗口window.add(s[right]);right++; //移动右边界while (符合要求) {// 缩小窗口window.remove(s[left]);left++; //移动左边界}
}


大致框架:

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {//不一定是这两个数据结构unordered_map need, window; //计数器用于判断窗口是否符合要求for (char c : t) need[c]++;int left = 0, right = 0; //初始化左右边界while (right < s.size()) {// c 是将移入窗口的字符char c = s[right];// 右移窗口right++;// 进行窗口内数据更新// 判断左侧窗口是否要收缩while (window needs shrink) {// d 是将移出窗口的字符char d = s[left];// 左移窗口left++;// 进行窗口内数据更新...}}
}

滑动窗口算法思路:

1、字符串 S 中使用左右指针,初始化 left = right = 0,左闭右开区间 [left, right) 称为一个「窗口」

2、先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含 T 中的所有字符)

3、此时,停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不再包含 T 中所有字符)。同时,每次增加 left,都要更新一轮结果(窗口长度)

4、重复第 2 、 3 步,直到 right 到达字符串 S 的尽头。

第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解

class Solution {
public:string minWindow(string s, string t) {// window为窗口中的字符,need为需要凑齐的字符unordered_map need, window;for(const auto &c : t)  ++need[c];int left = 0, right = 0; //初始化左右边界,左闭右开//每当need中有一个字符查全,count就加一//当count==need.size()时代表找到一个符合要求的窗口int count = 0; int start = 0, len = INT_MAX; //分别记录最小左边界和最小窗口长度while(right < s.size()){auto c = s[right]; //字符c即将进入窗口++right; // 右移窗口//字符进入窗口if(need.count(c)){//如果字符c是t中的字符++window[c]; //增加计数器if(window[c] == need[c]) //字符c已查全++count;}//判断左侧边界是否需要收缩,注意是while循环while(count == need.size()){ //窗口里所有字符的个数均达到要求//更新子串长度if(right - left < len){start = left; //记录最小时候的窗口左边界(后面需要输出子串)len = right - left;}//字符d即将移出窗口auto d = s[left];left++; //左移窗口// 进行窗口内数据的一系列更新if(need.count(d)){ //如果刚才移出的字符刚好也是t里面的字符if(window[d] == need[d])--count;--window[d];}//否则就继续增加左边界,直到窗口window的数据不再满足要求(count != need.size())}}return len == INT_MAX ? "" : s.substr(start, len);}
};

补充说明: if(need.count ( c )) 是一种 C++ 中 map 数据结构的使用方法。

map 是一种关联式容器,其中的元素是通过键值对(key-value)来存储和访问的。在 map
中,每个键(key)都唯一对应一个值(value),因此可以通过键来快速查找对应的值。

在这里,need 可以看作是一个存储目标字符串中指定字符的出现次数的 map,其中 key 为目标字符串中出现的字符,value
为该字符在目标字符串中出现的次数。need.count© 的作用是判断在 map need 中是否已经存在字符 c,如果存在则返回
1,否则返回 0。这里的返回值可以用于条件判断,例如:

    // 如果 c 存在于 need 中// ... } else {// 如果 c 不存在于 need 中// ... } ```更一般地,count() 函数可以用于判断 map 中是否存在某个键值对。如果存在,返回值为 1,否则返回值为
0。在实际使用中,count() 函数常常被用来判断 map 中是否存在某个键,以免出现访问不存在键值对的情况。

代码逻辑确实很清晰,我自己写的时候也是这么考虑,但是逻辑确实没这么条理清晰。

最后可以看这儿:leetcode 第 567 题:字符串的排列(C++)_qq_32523711的博客-CSDN博客

其实很多时候哈希表可以修改为数组,两个哈希表也可以用一个哈希表解决(一边增一边减,看最后是否为0),甚至一个数组。因为我们使用这些容器的目的只是为了计数,能够准确设计好计数细节,用什么容器都可以。

只使用一个哈希表,思路就是t用于增加计数,s中的子串用于减小计数,当计数为0的时候不就是对应字符查全的时候吗?:

class Solution {
public:string minWindow(string s, string t) {unordered_map need;for(auto c : t) ++need[c];int left = 0, right = 0;int count = 0;int start = 0; //用于记录符合要求的最小子串的开头int len = INT_MAX; //用于记录最小长度while(right < s.size()){auto c = s[right];++right;if(need.count(c)){--need[c];if(need[c] == 0)    ++count;}while(count == need.size()){if(right - left < len){start = left;len = right  - left;}auto c = s[left];++left;if(need.count(c)){if(need[c] == 0)--count;++need[c];}}}return len == INT_MAX ? "" : s.substr(start, len);}
};

总之我们的目标是将字符查全,通过容器计数判断某个字符是否查全,哈希表的优点是可以在容器中快速定位到该字符并获取计数值,缺点是没有数组简单高效。而快速定位到某个字符这个功能,数组经过特殊处理也是能实现的(比如将字符转化为ASCII码,以ASCII码作为数组下标,就能实现快速定位),所以使用数组理论上会更快。使用数组需要注意的是:

如果使用两个数组,那就很简单,思路和两个哈希表几乎一模一样;

如果使用一个数组,需要考虑怎么初始化数组的值,因为我们会对数组中特定位置的值进行增加和减小的操作,就会导致缩小左边界的时候(会减小值)某些字符对应的值小于0(这是可能的,因为可能有重复字符的出现),如果初始化做的不好,就很难将这些值和那些空位(非字符对应的数组下标)无法区分开来,这么说很抽象,还是看代码吧:

使用两个数组,之前哈希表耗时在60ms左右,数组的耗时则在10ms左右,确实快了很多:

class Solution {
public:string minWindow(string s, string t) {vector< int> need(58, 0), window(58, 0);//A(65) ~ z(122)范围内的ascii值范围for(auto c : t) ++need[c - 'A'];int left = 0, right = 0;int count = 0; //记录当前查全的字符个数int target = 0; //记录需要查全的字符个数for(auto c : need){if(c)   ++target;}int start = 0; //用于记录符合要求的最小子串的开头int len = INT_MAX; //用于记录最小长度while(right < s.size()){auto c = s[right] - 'A';++right;if(need[c] > 0){++window[c];if(need[c] == window[c])    ++count;}while(count == target){ //所有字符均查全if(right - left < len){start = left;len = right  - left;}auto c = s[left] - 'A';++left;if(need[c] > 0){if(need[c] == window[c])--count;--window[c];}}}return len == INT_MAX ? "" : s.substr(start, len);}
};

如果只使用一个数组:


```cpp
class Solution {
public:string minWindow(string s, string t) {vector< int> need(58, 0);//A(65) ~ z(122)范围内的ascii值范围for(auto c : t) ++need[c - 'A'];int left = 0, right = 0;int count = 0; //记录当前查全的字符个数int target = 0; //记录需要查全的字符个数for(auto &c : need){if(c)   ++target;//数组need里面我们只是在某些部分(有字符ascii码对应的部分)填充了值,其他部分都初始化为0//我们通过need[c]的值是否大于0就能区分两者//但是下面会涉及到--need[c]的操作,这样某些填充了值的地方就会小于0//那么我们上面的判断依据就不管用了,所以才需要将那些未填充的地方的值设置的完全可以和填充过的地方区分开来//比如未填充地方全部设为INT_MIN,你填充过的地方即使会减小,也不可能一直减小到INT_MIN吧//而实际上取多少其实无所谓,能区分开就行,取个-1000就能区分开else    c = -1000; }int start = 0; //用于记录符合要求的最小子串的开头int len = INT_MAX; //用于记录最小长度while(right < s.size()){auto c = s[right] - 'A';++right;if(need[c] > -1000){--need[c];if(need[c] == 0)    ++count;}while(count == target){ //所有字符均查全if(right - left < len){start = left;len = right  - left;}auto d = s[left] - 'A';++left;if(need[d] > -1000){if(need[d] == 0)--count;++need[d];}}}return len == INT_MAX ? "" : s.substr(start, len);}
};

其实数组占用空间本来就很小,使用两个数组就可以了,只使用一个数组运行效率上和使用两个的差别微乎其微,但是使用一个数组反而会更麻烦一点。

相关内容

热门资讯

什么是音韵学 什么是音韵学音韵学 [yīn yùn xué]音韵学,是一门研究汉语语音系统的科学,它包括古音学、今...
同情与爱 同情与爱我分不清同情与爱,怎么办?设想你同情的人永远离去时你的心情,与你爱的人永远离去时的心情,(只...
你如何评价陈雪凝? 你如何评价陈雪凝?陈雪凝太小了,在音乐这方面阅历不足,而且如果后期不走音乐专业单凭现在的水平的话不足...
陈潇的介绍 陈潇的介绍陈潇,汉族,湖南茶陵人。
求一篇穿越宅斗小说名称~剧情大... 求一篇穿越宅斗小说名称~剧情大概是。意千重的《喜盈门》和暮兰舟的《十八钗》,你说的内容在这两部小说中...
大班班级表演游戏多长时间最好 大班班级表演游戏多长时间最好大班班级表演游戏多长时间最好为25到30分钟。这是根据幼儿年龄特点,生理...
穿越女医苏小小宣承烨是什么书名 穿越女医苏小小宣承烨是什么书名素手狂医:嫡女太子妃
女子租屋门口被歹徒持刀挟持,室... 女子租屋门口被歹徒持刀挟持,室友开门看见,狠心反手把门关上,你怎么看?我们一直都在追求公平正义,也一...
狂欢永不落幕,“日不落帝国”西... 狂欢永不落幕,“日不落帝国”西班牙有哪些著名的节日?一月的“三王节”,号称是一年里最甜蜜的节日,三月...
钻石与石墨 钻石与石墨2/11 ~ 《次第花开》 无我有助于减弱对外物的贪执。比如,在一般人的价值观里,钻石和...
汉人王猛为何选择辅助苻坚,而不... 汉人王猛为何选择辅助苻坚,而不支持汉人正统的东晋王朝呢?在有能力人的眼里,汉人与正统都不过是虚名而已...
请谈谈心理咨询的主要内容有哪些... 请谈谈心理咨询的主要内容有哪些以及心理咨询时应注意的问题?请谈谈心理咨询的主要内容有哪些,以及心理咨...
英雄联盟复活甲怎么出 英雄联盟复活甲怎么出英雄联盟中复活甲即:守护天使,需要花费游戏中所获得金币购买。
新破天一剑 午灵星的极乐净土怎... 新破天一剑 午灵星的极乐净土怎么去?知道的告诉我,详细点。要需要一个马水,如果你不会武功的话,去就没...
人的软肋在哪? 人的软肋在哪?是人的骨骼 不知在哪里?肋骨前端也就是肋软骨图中灰白色部分 有文字标记背那,腰上些
怎样面对人性的恶 怎样面对人性的恶人性本恶,要通过不断修炼自己,改掉坏地方。
太累了也该歇歇了是哪首歌? 太累了也该歇歇了是哪首歌?刘欢《温情永远》也许是因为每天都相间生活有点平淡也许是由于彼此太了解觉得不...
求亮剑最后一集李云龙等三人的演... 求亮剑最后一集李云龙等三人的演讲稿只有都粱知道啊这么著,你去买一本亮剑书,那里可能有。
找两本好看的校园小说 找两本好看的校园小说恋上恶魔的吻
谁有法老的宠妃第二部和第三部完... 谁有法老的宠妃第二部和第三部完整版啊你可以到派派小说论坛看看,基本上都有好像比第一部稍微弱一点诶,但...