(Java) LeetCode 30. Substring with Concatenation of All Words —— 与所有单词相关联的字串

You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in sthat is a concatenation of each word in words exactly once and without any intervening characters.

Example 1:

Input:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.  

Example 2:

Input:
  s = "wordgoodstudentgoodword",
  words = ["word","student"]
Output: []

一道字符串匹配问题。虽然标记是Hard,但想要做出来并不难,因为思路很直观。首先把字典里的单词放到HashMap<String, Integer>中,其中Integer部分存储此单词在字典中出现的次数。遍历字符串的每一个位置,如果是以字典中的某一个单词为前缀,那么继续检查前缀后面剩下的字符串,同时把字典中对应的出现次数减一。当字典中单词出现次数为零的时候证明这个单词已经匹配完毕,那就从字典中删掉。如果字典空了代表所有单词已经全都被匹配,证明这个位置起始的字符串是符合条件的,那么就把起始位置加入结果,之后继续迭代下一个位置。暴力解法一,思路很清晰,代码实现起来就用递归去减少代码量。简洁,清晰,只是…只击败了可怜的5%…

看了排名第一的解法,茅塞顿开。其中利用到了“滑动窗口”的思想,也有点像KMP。思路是假设字典中每个单词的长度为size,那么以size为窗口大小来滑动窗口。如果有单词加入,就扩大窗口右边界,如果单词不连续,就缩小窗口左边界。这样做的好处是,同一step下的所有满足条件的答案都可以一次性的加到结果中,并且是以size作为step扫描的,效率大大提高。大概的思路是,和解法一一样需要把字典放到HashMap里面,以字典中单词长度size为step,依次扫描字符串对应的位置,比如例一,分别扫描1, 4, 7.../2, 5, 8.../3, 6, 9...。维持一个初始大小为size的窗口,左边界为每次扫描字符串的起始位置,右边界根据每次扫描到的子串单词决定。建立一个新的HashMap记录当前窗口里的单词。如果接下来的子串单词在字典中,那么把他加入到窗口字典。如果窗口字典中单词个数恰好是字典中单词总数,且每个单词对应的出现次数完全一致,那么这就是一个符合要求的匹配。如果扫描到的子串不在字典中,或者窗口字典中单词出现次数已经超过了原本字典单词的出现次数,那就要调整左边界和窗口字典,使匹配能继续进行。详见下文代码。


解法一(Java)

class Solution {   
    public List<Integer> findSubstring(String s, String[] words) {
        if (s.length() == 0 || words == null || words.length == 0) return new ArrayList<>();
        List<Integer> res = new ArrayList<>();
        int size = words[0].length(), len = words.length;
        for (int i = 0; i <= s.length() - len; i++) {
            HashMap<String, Integer> m = new HashMap<>();
            for (int j = 0; j < words.length; j++) 
                m.put(words[j], m.getOrDefault(words[j], 0) + 1);
            if(check(s, i, m, size)) res.add(i);
        }
        return res;
    }
    
    private boolean check(String s, int i, HashMap<String, Integer> m, int size) {
        if (m.size() == 0) return true;
        if (i > s.length() || i + size > s.length()) return false;
        String prefix = s.substring(i, i+size);
        if (m.containsKey(prefix) && m.get(prefix) > 0) {
            m.put(prefix, m.get(prefix)-1);
            if (m.get(prefix) == 0) m.remove(prefix);
            return check(s, i+size, m, size);
        }
        else return false;
    }
}

解法二(Java)

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        if (s.length() == 0 || words == null || words.length == 0) return new ArrayList<>();
        List<Integer> res = new ArrayList<>();
        int size = words[0].length(), len = words.length;
        HashMap<String, Integer> m = new HashMap<>();
        HashMap<String, Integer> curM = new HashMap<>();
        for (int j = 0; j < words.length; j++) 
            m.put(words[j], m.getOrDefault(words[j], 0) + 1);
        for (int i = 0; i < size; i++) {
            int start = i;
            int count = 0;
            curM.clear();
            for (int j = i; j <= s.length() - size; j += size) {            
                String cur = s.substring(j, j + size);
                if (m.containsKey(cur)) {
                    curM.put(cur, curM.getOrDefault(cur, 0) + 1);
                    count++;
                    while (curM.get(cur) > m.get(cur)) {
                        String left = s.substring(start, start + size);
                        curM.put(left, curM.get(left) - 1);
                        count--;
                        start += size;
                    }
                    if (count == len) {
                        res.add(start);
                        String left = s.substring(start, start + size);
                        curM.put(left, curM.get(left) - 1);
                        count--;
                        start += size;
                    }
                }
                else {
                    start = j + size;  
                    curM.clear();
                    count = 0;
                }
            }
        }
        return res;
    }
}
原文地址:https://www.cnblogs.com/tengdai/p/9307936.html