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; } }