
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)。