Loading

力扣 - 30. 串联所有单词的子串

题目

30. 串联所有单词的子串

思路1(滑动窗口+哈希表)

  • 使用HashMap,这样子就可以不用纠结字符串的顺序了
  • 先计算words中的每个单词出现的个数
  • 每次确定starts位置,然后依次截取wordLen长度的字串,比较是否再words中,不存在的话,直接break,进入下一个窗口;存在的话,就加入到窗口中,添加进去还要比较是否数量抄了,如果超了的话也是不行的
  • 遍历字符串s,每次每个窗口的最大容量为words的大小,每次的窗口用一个新的哈希表来表示
  • 移动窗口时候右移一个字符

代码

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        // 获取s字符串长度
        int length = s.length();
        // 获取words中的单词个数
        int wordNum = words.length;
        // 如果字符串长度为0或者words中的单词个数为0,直接返回空结果
        if (length == 0 || wrodNum == 0) {
            return res;
        }
        // 获取words中的单词长度
        int wordLen = words[0].length();
        // 统计words中的每个不同单词出现个数
        Map<String, Integer> wordCount = new HashMap<>();
        for (String word : words) {
            wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
        }

        // 从0开始,末尾只要道 strLen - wordNum * wordLen + 1 即可
        // wordNum * wordLen 是待匹配的单词串联总长度,末尾的 +1 是因为如果刚好最后一段匹配上,如果不 +1 ,那么就会导致最后一段匹配不了
        // 滑动窗口每次向右移动一格
        for (int i = 0; i < strLen - wordNum * wordLen + 1; i++) {
            // 用于临时存储滑动窗口中的子字符串及其对应的出现次数
            Map<String, Integer> temp = new HashMap<>();
            // num用于统计临时map中加入了多少个字符串
            int num = 0;
            // 记录此次遍历窗口的起始位置
            int start = i;
            // 当temp中的单词数量没有超过待串联的单词数量时,就可以继续添加
            while (num < wordNum) {
                // 获取一个wordLen长度的子字符串
                String subWord = s.substring(start, start + wordLen);
                // 如果待串联的单词中包含该单词,就添加到temp哈希表中
                if (wordCount.containsKey(subWord)) {
                    temp.put(subWord, temp.getOrDefault(subWord, 0) + 1);
                    // 如果一个单词出现次数超过了待串联的这个单词的最多个数,那么也直接不匹配
                    if (temp.get(subWord) > wordCount.get(subWord)) {
                        break;
                    }
                } else {
                    // 不包含subWord就直接进入下一个滑动窗口
                    break;
                }
                // 添加的word数+1
                num++;
                // start也右移wordLen,为了获取下一个subWord
                start += wordLen;
            }
            // 如果每个都符合,且添加到temp哈希表中,那么就该字串符合条件,将起始索引添加到res集合中
            if (num == wordNum) {
                res.add(i);
            }
        }
        // 得到结果
        return res;
    }
}

复杂度分析

  • 时间复杂度:\(O(N*M)\),其中 N 为字符串s的长度, M 为每个单词的长度
  • 空间复杂度:\(O(M)\),M 单词的个数,哈希表大小

思路2(滑动窗口优化)

  • 第一种思路是每次窗口只向右移动一个,然后再在每个窗口里面截取wordLen的字串,这样的复时间杂度很高m*n
  • 为什么要每次只能移动一个呢?因为如果s字符串单词大小如果有的不为word的长度,那么按照word长度为跨度来移动窗口就会导致匹配不到正确结果。例:s="afoobar" words=["foo", "bar"],此时afo不匹配,接下来是oba也不匹配,可是正确结果是有匹配的,这就导致了bug
  • 我们介绍另一种方法可以实现窗口每次移动的跨度为word的长度,但是还要分wordLen种情况,此时就符合题意了

代码

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        int length = s.length();
        int wordNum = words.length;
        if (length == 0 || wordNum == 0) {
            return res;
        }
        int wordLen = words[0].length();
        Map<String, Integer> wordCount = new HashMap<>();
        for (String word : words) {
            wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
        }

        // 将所有移动分成wordLen类情况
        for (int i = 0; i < wordLen; i++) {
            Map<String, Integer> temp = new HashMap<>();
            // 记录当前哈希表种的字串个数,且不能超过wordNum
            int num = 0;
            // 从i开始计算,且窗口每次右移wordLen长度
            for (int j = i; j < length - wordLen * wordNum + 1; j += wordLen) {
                // 用于标记是否出现单词符合,但是个数超过了的情况
                boolean hasRemoved = false;

                while (num < wordNum) {
                    // 动态获取字串
                    String subWord = s.substring(j + num * wordLen, j + (num+1) * wordLen);
                    // 如果字串在words中,就添加
                    if (wordCount.containsKey(subWord)) {
                        temp.put(subWord, temp.getOrDefault(subWord, 0) + 1);
                        // 并且判断是否超过了words中的个数
                        if (temp.get(subWord) > wordCount.get(subWord)) {
                            // 字串存在words中,但数量超了,要删减,将hasRemoved置为true
                            hasRemoved = true;
                            // 记录删除了几个字串
                            int removeNum = 0;
                            // 只要个数还是大于words中的个数就一直删除窗口的最前面的字串
                            while (temp.get(subWord) > wordCount.get(subWord)) {
                                // 获取当前窗口最前面的字串
                                String firstWord = s.substring(j + removeNum * wordLen, j + (removeNum+1) * wordLen);
                                // -1即删除
                                temp.put(firstWord, temp.get(firstWord) - 1);
                                // 同时记录删除了一个字串
                                removeNum++;
                            }
                            // +1因为已经将那个超出个数的那个字串加入到哈希表中了,removeNum是代表删除了多少个字串
                            num = num - removeNum + 1;
                            // 然后将j的值重新修改了
                            j = j + (removeNum - 1) * wordLen;
                            break;
                        }
                    } else {
					  // 如果字串不在words中,那么窗口起始位置就可以直接跳到这个字串之后位置就可以了
                        j = j + num * wordLen;
                        // 清空窗口(哈希表)
                        temp.clear();
                        // 将num计数置为0
                        num = 0;
                        break;
                    }
                    num++;
                }

                // 如果匹配的话,那么num就会等于words
                if (num == wordNum) {
                    res.add(j);
                }

                // 如果字串完全匹配,那么将窗口的最前面的一个字串删除,进入下一轮判断
                if (num > 0 && !hasRemoved) {
                    String subWord = s.substring(j, j + wordLen);
                    temp.put(subWord, temp.get(subWord)-1);
                    num--;
                }
            }
        }
        return res;
    }
}

复杂度分析

  • 时间复杂度:\(O(N)\),其中 N 为字符串 s 的长度
  • 空间复杂度:\(O(M)\),其中 M 为单词的个数
posted @ 2020-11-26 01:03  linzeliang  阅读(131)  评论(0)    收藏  举报