刷题笔记:力扣第76题-最小覆盖子串

1.本题题意比较好理解,首先想到的方法便是滑动窗口+哈希表。首先将字符串t的所有字符遍历一遍,将次数存入哈希表中。然后开始遍历字符串s,核心逻辑为"先包含再舍弃",即先让右指针尽可能前进,直到包含所有所需的字符。然后对现在的窗口进行判断,看是否为更小窗口;且让左指针前进,途中舍弃其他字符,不断缩小窗口边界,直到舍弃一个所需的字符导致目前的窗口不再符合题意,循环该过程。最后将记录的左右边界的字符串拷贝到结果中。

2.左指针指向窗口的第一个字符,右指针指向窗口下一个待包含字符,按照区间关系即为"左闭右开"。

3.基于以上思想,可写出完整代码如下:

cpp 复制代码
 1. // uthash哈希节点,key存储字符ASCII码,cnt存储字符需要的数量
 2. typedef struct {
 3.     int key;
 4.     int cnt;
 5.     UT_hash_handle hh;
 6. } HashEntry;
 7.  
 8. // 最小覆盖子串,滑动窗口+uthash哈希实现
 9. char* minWindow(char* s, char* t) {
10.     // 分配结果字符串空间
11.     char* res = (char*)malloc(sizeof(char) * 100001);
12.     int lens = strlen(s);
13.     int lent = strlen(t);
14.     // s长度小于t,不可能存在覆盖子串,返回空串
15.     if (lens < lent){
16.         res[0] = '\0';
17.         return res;
18.     }
19.     HashEntry* hashTable = NULL;
20.     // 统计t中每个字符需要的数量,存入哈希表
21.     for (int i = 0; i < lent; i++){
22.         HashEntry* entry = NULL;
23.         int tmp = t[i];
24.         HASH_FIND_INT(hashTable, &tmp, entry);
25.         if (entry == NULL){
26.             entry = (HashEntry*)malloc(sizeof(HashEntry));
27.             entry->key = t[i];
28.             entry->cnt = 1;
29.             HASH_ADD_INT(hashTable, key, entry);
30.         } else {
31.             entry->cnt++;
32.         }
33.     }
34.     // cur_len:窗口内已经满足数量要求的字符个数
35.     int cur_len = 0;
36.     // 滑动窗口左右边界
37.     int l = 0, r = 0;
38.     // 记录最小子串的左右边界
39.     int min_l = 0, min_r = INT_MAX;
40.     // 右指针不断向右扩展窗口
41.     while (r < lens){
42.         HashEntry* entry = NULL;
43.         int tmp = s[r];
44.         HASH_FIND_INT(hashTable, &tmp, entry);
45.         if (entry){
46.             if (entry->cnt > 0){
47.                 cur_len++;
48.             }
49.             entry->cnt--;
50.         }
51.         r++;
52.         // 当前窗口已经全部覆盖t,尝试收缩左边界寻找更短子串
53.         while (cur_len == lent){
54.             // 更新最小窗口
55.             if (r - l < min_r - min_l){
56.                 min_l = l;
57.                 min_r = r;
58.             }
59.             
60.             tmp = s[l];
61.             HASH_FIND_INT(hashTable, &tmp, entry);
62.             if (entry){
63.                 entry->cnt++;
64.                 if (entry->cnt > 0){
65.                     cur_len--;
66.                 }
67.             }
68.             l++;
69.         }
70.     }
71.     // 没有找到符合条件子串返回空字符串,否则截取子串
72.     if (min_r == INT_MAX){
73.         res[0] = '\0';
74.     } else {
75.         strncpy(res, s + min_l, min_r - min_l);
76.         res[min_r - min_l] = '\0';
77.     }
78.     return res;
79. }

该算法时间复杂度为O(m + n),空间复杂度为O(m)(哈希表最多存m个元素)。

3.需要注意,本题的结果窗口完全可能远比lent要大,所以不能像以往的题目那样只给res分配lent + 1的空间!

4.只要题目是关于"英文字母"或"ASCII字符"的,完全可以不使用复杂的哈希表,直接用一个大小为128的整型数组int hash128即可完美代替。改写之后完整代码如下:

cpp 复制代码
 1. // 最小覆盖子串,滑动窗口+数组哈希(ASCII 128字符数组)
 2. char* minWindow(char* s, char* t) {
 3.     // lens为s的长度,lent为t的长度
 4.     int lens = strlen(s);
 5.     int lent = strlen(t);
 6.     // 分配结果字符串内存
 7.     char* res = (char*)malloc(sizeof(char) * 100001);
 8.     // s比t短,不可能存在覆盖子串,直接返回空串
 9.     if (lens < lent){
10.         res[0] = '\0';
11.         return res;
12.     }
13.     // hash数组:记录t中每个字符需要的数量
14.     int hash[128] = {0};
15.     for (int i = 0; i < lent; i++){
16.         hash[t[i]]++;
17.     }
18.     // window数组:记录当前滑动窗口内对应字符的数量
19.     int window[128] = {0};
20.     // cur_len:窗口中满足t字符数量要求的字符总数
21.     int cur_len = 0;
22.     // l、r为滑动窗口左右指针
23.     int l = 0, r = 0;
24.     // min_l、min_r记录最小窗口的左右边界
25.     int min_l = 0, min_r = INT_MAX;
26.     // 右指针不断向右扩大窗口
27.     while (r < lens){
28.         // 当前字符是t中需要的字符
29.         if (hash[s[r]] != 0){
30.             // 窗口内该字符数量还没达到t的要求,有效字符计数+1
31.             if (hash[s[r]] > window[s[r]]){
32.                 cur_len++;
33.             }
34.             window[s[r]]++; 
35.         }
36.         r++;
37.         // 当前窗口已经完全覆盖t,尝试收缩左边界,寻找更短窗口
38.         while (cur_len == lent){
39.             // 更新最小窗口边界
40.             if (min_r - min_l > r - l){
41.                 min_l = l;
42.                 min_r = r;
43.             }
44.             // 左指针指向的字符是t中需要的字符
45.             if (hash[s[l]] != 0){
46.                 // 如果窗口内该字符刚好等于需要数量,移除后不再满足,有效计数减1
47.                 if (hash[s[l]] == window[s[l]]){
48.                     cur_len--;
49.                 }
50.                 window[s[l]]--;
51.             }
52.             l++;
53.         }
54.     }
55.     // 没有找到合法子串,返回空字符串;否则截取最小窗口字符串
56.     if (min_r == INT_MAX){
57.         res[0] = '\0';
58.         return res;
59.     } else {
60.         strncpy(res, s + min_l, min_r - min_l);
61.         res[min_r - min_l] = '\0';
62.     }
63.     return res;
64. }

该算法时间复杂度为O(m + n),空间复杂度为O(1)。

相关推荐
sbjdhjd6 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
UIU1148 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
论文复现现场8 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
ZhengEnCi8 小时前
用 AI 写代码为什么拼到最后还是拼人:算力瓶颈与领域专家提示词的本质差异
算法·架构
海绵宝宝的月光宝盒9 小时前
14-AutoCAD-阵列命令应用与案例实战(AR)
经验分享·笔记·自动化·课程设计·制造·学习方法·cad
秋天的怀念_David9 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
碧海银沙音频科技研究院10 小时前
在PC电脑上用Whisper→Marian算法实现离线语音翻译功能
算法·whisper
niyongsheng10 小时前
3D 装箱算法工程化:数据结构、放置启发式与支撑规则
数据结构·算法·rust
白山编程大哥10 小时前
接口幂等性是什么?如何设计?
笔记
ACP广源盛1392462567310 小时前
6 盘位存储扩展方案选型笔记:YLB3116 @ACP评估记录
笔记·硬件架构·硬件工程·国产芯片