刷题笔记:力扣第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)。

相关推荐
hengmeida1 小时前
楼宇控制柜标准化项目交付完整规范
大数据·网络·笔记
Zane19941 小时前
比较排序跑不出O(nlogn)是数学上证明过的,计数、桶、基数排序凭什么能绕开这堵墙
算法·排序算法
tiger8651 小时前
大语言模型面试和题解,梳理中国主流开源 LLM 系列的发展脉络、技术路线与工程取舍
人工智能·gpt·深度学习·算法·自然语言处理·面试·transformer
我不会起名字3221 小时前
一天一道力扣Hot100(36):深度优先算法---组合总和
数据结构·c++·后端·python·算法·go
蒸蒸yyyyzwd1 小时前
cpp 选手秋招学习笔记 day40
笔记·八股
有范先生1 小时前
速食米饭:如何避开闷味、干硬的速食主食
笔记
长葡萄的叶子1 小时前
AI Agent 中的 Skill:从 Tool 调用到任务能力封装
笔记·skill
虚无的纽扣2 小时前
【力扣刷题】第三天:有效三角形的个数、快乐数
算法·排序算法
tiger8652 小时前
大语言模型面试和题解,Context Engineering 怎么做?长 Prompt、动态上下文与 Memory 管理
人工智能·深度学习·算法·语言模型·自然语言处理·面试·nlp