LeetCode 30:串联所有单词的子串

87 阅读2分钟

题目

image.png

思路

多起点的滑动窗口

  • 注意到单词为无序串联,所以所需要的查找与单词顺序无关,只与单词的数量有关。
  • 用哈希表 vm 存储 words 中每个单词出现的频次。
  • 在串 s 中运用滑动窗口,每次滑动的长度为一个单词的长度 d,同时用哈希表记录窗口中单词出现的频次。
  • 随着窗口的滑动,判断 vm 与此时窗口的哈希表是否相等,若相等,则保存此时的结果。
  • 初始化 d 个滑动窗口,即 d 个起点的滑动窗口,可以保证不遗漏

代码实现

class Solution {
public:
    std::vector<int> findSubstring(std::string s, std::vector<std::string>& words) {
        int length = s.length();    // 总串的长度
        int d = words[0].length();  // 每个单词的长度
        int m = words.size();       // 要查找的单词个数
        int l = d * m;              // 要查找的串的长度

        std::vector<int> res;       // 保存所有结果

        std::unordered_map<std::string, int> um;   // 存放words中每个单词的词频
        for (const auto& word : words)
        {
            um[word]++;
        }

        // 初始化 d 个滑动窗口
        std::vector<std::unordered_map<std::string, int>> v_um(d);
        for (int i = 0; i < d && i <= length - l; i++)  // length - l 为 i 最后一个可能的位置,若超过,则后面的串必然不可能有需要查找的串的长度
        {
            for (int j = i; j < i + l; j += d)          // i 到 i + l 是窗口的大小 
            {
                std::string str = s.substr(j, d);
                v_um[i][str]++;     // 哈希表记录此时创建的单词频次
            }

            if (v_um[i] == um)      // 判断窗口内的单词词频是否和目标词频相等
                res.push_back(i);
        }

        // 滑动窗口
        for (int i = d; i <= length - l; i++)           // 第一个窗口已经判断过,从第二个窗口开始判断
        {
            int r = i % d;
            std::string wl = s.substr(i - d, d);        // 左侧的旧单词
            std::string wr = s.substr(i + l - d, d);    // 右侧的新单词
            if (--v_um[r][wl] == 0)                     // 更新窗口后需要更新相应的哈希表,如果单词频次降为0,则从哈希表中删除该键
                v_um[r].erase(wl);
            v_um[r][wr]++;                              // 将右侧单词加入哈希表

            // 判断窗口内的单词词频是否和目标词频相等
            if (v_um[r] == um)
                res.push_back(i);
        }

        return res;
    }
};