力扣第三十题——串联所有单词的子串

内容介绍

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

  • 1 <= s.length <= 104
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i] 和 s 由小写英文字母组成

完整代码

 typedef struct {
    char key[32];
    int val;
    UT_hash_handle hh;
} HashItem;

int* findSubstring(char * s, char ** words, int wordsSize, int* returnSize){    
    int m = wordsSize, n = strlen(words[0]), ls = strlen(s);
    int *res = (int *)malloc(sizeof(int) * ls);
    int pos = 0;
    for (int i = 0; i < n; i++) {
        if (i + m * n > ls) {
            break;
        }
        HashItem *diff = NULL;
        char word[32] = {0};
        for (int j = 0; j < m; j++) {
            snprintf(word, n + 1, "%s", s + i + j * n);
            HashItem * pEntry = NULL;
            HASH_FIND_STR(diff, word, pEntry);
            if (NULL == pEntry) {
                pEntry = (HashItem *)malloc(sizeof(HashItem));
                strcpy(pEntry->key, word);
                pEntry->val = 0;
                HASH_ADD_STR(diff, key, pEntry);
            } 
            pEntry->val++;            
        }
        for (int j = 0; j < m; j++) {
            HashItem * pEntry = NULL;
            HASH_FIND_STR(diff, words[j], pEntry);
            if (NULL == pEntry) {
                pEntry = (HashItem *)malloc(sizeof(HashItem));
                strcpy(pEntry->key, words[j]);
                pEntry->val = 0;
                HASH_ADD_STR(diff, key, pEntry);
            } 
            pEntry->val--;
            if (pEntry->val == 0) {
                HASH_DEL(diff, pEntry);
                free(pEntry);
            }
        }
        for (int start = i; start < ls - m * n + 1; start += n) {
            if (start != i) {
                char word[32];
                snprintf(word, n + 1, "%s", s + start + (m - 1) * n);
                HashItem * pEntry = NULL;
                HASH_FIND_STR(diff, word, pEntry);
                if (NULL == pEntry) {
                    pEntry = (HashItem *)malloc(sizeof(HashItem));
                    strcpy(pEntry->key, word);
                    pEntry->val = 0;
                    HASH_ADD_STR(diff, key, pEntry);
                } 
                pEntry->val++;
                if (pEntry->val == 0) {
                    HASH_DEL(diff, pEntry);
                    free(pEntry);
                }
                snprintf(word, n + 1, "%s", s + start - n);
                pEntry = NULL;
                HASH_FIND_STR(diff, word, pEntry);
                if (NULL == pEntry) {
                    pEntry = (HashItem *)malloc(sizeof(HashItem));
                    strcpy(pEntry->key, word);
                    pEntry->val = 0;
                    HASH_ADD_STR(diff, key, pEntry);
                } 
                pEntry->val--;
                if (pEntry->val == 0) {
                    HASH_DEL(diff, pEntry);
                    free(pEntry);
                }
            }
            if (HASH_COUNT(diff) == 0) {
                res[pos++] = start;
            }
        }
        HashItem *curr, *tmp;
        HASH_ITER(hh, diff, curr, tmp) {
            HASH_DEL(diff, curr);  
            free(curr);      
        }
    }
    *returnSize = pos;
    return res;
}

思路详解

整体思路

  1. 初始化:确定单词长度n,单词数量m,字符串s的长度ls,并分配一个数组res用于存储结果。

  2. 遍历所有可能的起始位置:由于每个单词的长度是n,所以可能的起始位置最多有n个。

  3. 构建哈希表:对于每个起始位置,构建一个哈希表来存储words数组中每个单词出现的次数。

  4. 检查子串:从当前起始位置开始,每次移动一个单词长度n,检查当前子串是否由words中的单词组成。

  5. 更新哈希表:每次移动子串时,更新哈希表中的计数,如果计数为0,则从哈希表中删除。

  6. 记录结果:如果哈希表为空,说明找到了一个符合条件的子串,记录其起始索引。

  7. 清理哈希表:在每次循环结束后,清理哈希表,释放内存。

详细步骤

  1. 初始化变量mwords数组的长度,n是每个单词的长度,ls是字符串s的长度。res数组用于存储结果,pos用于记录结果数组的当前索引。

  2. 外层循环:遍历所有可能的起始位置i,最多有n个。如果起始位置加上m * n超过s的长度,则结束循环。

  3. 构建哈希表:对于每个起始位置,创建一个哈希表diff,并初始化它,将words数组中的每个单词加入哈希表,并设置其值为1。

  4. 内层循环:从当前起始位置开始,每次向前移动一个单词长度n,进行以下操作:

    • 如果不是起始位置,则更新哈希表,移除当前子串的第一个单词,并添加新的单词。
    • 检查哈希表是否为空,如果为空,则说明找到了一个符合条件的子串,记录其起始索引。
  5. 清理哈希表:在每次外层循环结束后,遍历哈希表,删除所有条目,并释放内存。

  6. 返回结果:将结果数组的长度赋值给returnSize,并返回结果数组res

代码注释说明

  • HashItem:定义了一个哈希表项,包含一个字符串key和一个整数值val,以及一个哈希表句柄hh
  • findSubstring:主函数,用于找到所有字母异位词的起始索引。
  • snprintf:用于从字符串s中提取子串。
  • HASH_FIND_STR:在哈希表中查找一个字符串。
  • HASH_ADD_STR:向哈希表添加一个字符串。
  • HASH_DEL:从哈希表中删除一个项。
  • HASH_COUNT:计算哈希表中的项数。

知识点精炼

哈希表应用

  1. 哈希表结构:使用结构体HashItem存储单词及其出现次数,利用UT_hash_handle进行哈希表操作。
  2. 哈希表操作:掌握HASH_FIND_STRHASH_ADD_STRHASH_DEL等宏,用于查找、添加和删除哈希表项。

字符串处理

  1. 子串提取:使用snprintf函数从源字符串中提取指定长度的子串。
  2. 字符串比较:利用哈希表进行字符串比较,避免直接使用字符串比较函数。

滑动窗口

  1. 窗口大小:窗口大小为单词数组words的总长度,即m * n
  2. 窗口移动:每次移动一个单词长度n,更新哈希表,进行窗口内字符串的检查。

双指针技术

  1. 起始指针:外层循环控制窗口的起始位置。
  2. 移动指针:内层循环控制窗口的移动,更新哈希表。

内存管理

  1. 动态分配:使用malloc为结果数组分配内存。
  2. 内存释放:使用free释放哈希表项和结果数组的内存。

循环与条件判断

  1. 边界检查:确保子串起始位置不会超出字符串s的长度。
  2. 哈希表检查:通过HASH_COUNT判断哈希表是否为空,以确定是否找到符合条件的子串。

性能优化

  1. 减少重复操作:通过只在窗口移动时更新哈希表,减少不必要的操作。
  2. 及时清理:在每次外层循环结束后清理哈希表,避免内存泄漏。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值