LeetCode第30题
/*
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 s that 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 = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
Output: []
*/
import java.util.*;
import java.lang.*;
public class SubstringConcatenationAllWords{
public static void main(String[] args){
String s = "barfoothebar";
String[] words = {"bar","foo","the"};
SubstringConcatenationAllWords scaw = new SubstringConcatenationAllWords();
List<Integer> result = scaw.findSubstring(s,words);
for(Integer temp : result){
System.out.println(temp);
}
}
// 遍历子串中所有的word,然后利用两个HashMap分别用于检查和计数,避免使用双层循环
// 在以上的基础上,对子串匹配问题分三类情况:符合要求、不符合要求1(重复)、不符合要求2(异常)
// 处理两种不符合要求情况的逻辑,最后做符合要求情况的判断(num == words.length)
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
if (s == null || words.length == 0) return res;
// words中所有的单词等长
int wordLen = words[0].length();
HashMap<String, Integer> allWords = new HashMap<String, Integer>();
for (String w : words) {
int value = allWords.getOrDefault(w, 0);
allWords.put(w, value + 1);
}
//将所有移动分成 wordLen 类情况
for (int j = 0; j < wordLen; j++) {
HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
//每次移动一个单词长度
int num = 0;
for (int i = j; i < s.length() - words.length * wordLen + 1; i = i + wordLen) {
while (num < words.length) {
// 或子串第num个用于比较的单词
String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
// word不在allWords中:情况1和情况3。word在allWords中:情况2.
if (allWords.containsKey(word)) {
num++;
// 更新hasWords中单词的个数
int value = hasWords.getOrDefault(word, 0);
hasWords.put(word, value + 1);
//出现情况三,遇到了符合的单词,但是次数超了
if (hasWords.get(word) > allWords.get(word)) {
//从子串头开始移除单词,直到hasWords中不符合单词次数限制的单词符合要求
int removeNum = 0;
while (hasWords.get(word) > allWords.get(word)) {
// 取出从子串头开始的第removeNum个单词
String tempWord = s.substring(i + removeNum * wordLen, i + (removeNum + 1) * wordLen);
// 更新hasWords中单词的个数
int v = hasWords.get(tempWord);
hasWords.put(tempWord, v - 1);
removeNum++;
}
// 更新符合要求单词个数,得到符合要求的num数
num = num - removeNum ;
//这里依旧是考虑到了最外层的 for 循环,看情况二的解释。i指向出现问题的单词
i = i + (removeNum - 1) * wordLen;
break;
}
//出现情况二,遇到了不匹配的单词,直接将 i 移动到该单词的后边(但其实这里
//只是移动到了出现问题单词的地方,因为最外层有 for 循环, i 还会移动一个单词
//然后刚好就移动到了单词后边)
} else {
hasWords.clear();
// 这里的num是目前为止符合要求的num数。i指向出现问题的单词
i = i + num * wordLen;
// 更新符合要求单词个数,得到符合要求的num数
num = 0;
break;
}
}
//只要出现情况2和情况3,num的个数就会小于words.length
//出现情况一,子串完全匹配,我们将上一个子串的第一个单词从 hasWords 中移除
if (num == words.length) {
// 此时没有出现问题的单纯,即i已经指向“出现问题”的单词
res.add(i);
String firstWord = s.substring(i, i + wordLen);
int v = hasWords.get(firstWord);
hasWords.put(firstWord, v - 1);
num = num - 1;
}
// 到这里i指向出现问题的单词,for循环的i = i + wordLen可以跳过出现问题的单纯
}
}
return res;
}
// 遍历子串中所有的word,然后利用两个HashMap分别用于检查和计数,避免使用双层循环
public List<Integer> findSubstring03(String s, String[] words) {
List<Integer> ret = new ArrayList<Integer>();
if (s == null || words.length == 0) return ret;
// 将所有单词存储在HashMap中
HashMap<String, Integer> allWords = new HashMap<String, Integer>();
for (String w : words) {
int value = allWords.getOrDefault(w, 0);
allWords.put(w, value + 1);
}
// words中的单词是等长的
int wordLen = words[0].length();
// 循环遍历s,子串的初始索引要满足最小子串长度要求
for (int i=0;i<s.length() - words.length * wordLen + 1;i++){
HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
// 循环的次数为words的长度
int num = 0;
while(num < words.length){
// 获得s子串对应的第num个单词
String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
// 检查word是否在allWords中
if (allWords.containsKey(word)) {
// 更新hasWords对应单词的个数
int value = hasWords.getOrDefault(word, 0);
hasWords.put(word, value + 1);
//判断hasWords中单词的个数是否超过allWords的限制
if (hasWords.get(word) > allWords.get(word)) break;
} else {
// word不在allWords中,当前子串不和条件,跳出
break;
}
// 只有当前word数符合要求mum才会++
num++;
}
if (num == words.length) ret.add(i);
}
return ret;
}
public List<Integer> findSubstring02(String s, String[] words) {
List<Integer> ret = new ArrayList<Integer>();
if (s == null || words.length == 0) return ret;
// 循环遍历s
for (int i=0;i<s.length();i++){
List<Integer> temp = new ArrayList<Integer>();
for (int j=0;j<words.length;j++){temp.add(j);}
int cur = i;
int count = 0;
int ite = 0;
int offset = i;
while(ite < words.length){
// 匹配words.length次,若不能完全匹配则跳出
ite++;
// 遍历words集合,寻找是否有符合要求的word
for(Integer index:temp){
boolean ok = false;
// 判断当前单词是否可以接上
if (cur + words[index].length() -1 < s.length() &&
words[index].equals(s.substring(cur,cur + words[index].length()))){
ok = true;
cur += words[index].length();
}
if (ok){
// 当前单词满足要求count++,移除单词索引
count++;
// 更新再次比较的起始位
offset += words[index].length();
temp.remove(index);
break;
}else{
// 当前单词不符合则将比较索引归位
cur = offset;
}
}
// 第一遍没有找到符合要去的单词则跳出匹配
if (count < 1) break;
if (count == words.length) ret.add(i);
}
}
return ret;
}
public List<Integer> findSubstring01(String s, String[] words) {
List<Integer> ret = new ArrayList<Integer>();
if (s == null || words.length == 0) return ret;
// 循环遍历s
for (int i=0;i<s.length();i++){
List<Integer> temp = new ArrayList<Integer>();
for (int j=0;j<words.length;j++){temp.add(j);}
int cur = i;
int count = 0;
int ite = 0;
int offset = i;
while(ite < words.length){
// 匹配words.length次,若不能完全匹配则跳出
ite++;
// 遍历words集合,寻找是否有符合要求的word
for(Integer index:temp){
boolean ok = false;
for(int k=0;k<words[index].length() && cur < s.length();k++,cur++){
if (words[index].charAt(k) != s.charAt(cur)) break;
if (k == words[index].length() - 1) ok = true;
}
if (ok){
// 当前单词满足要求count++,移除单词索引
count++;
offset += words[index].length();
temp.remove(index);
break;
}else{
// 当前单词不符合则将比较索引归位
cur = offset;
}
}
if (count == words.length) ret.add(i);
}
}
return ret;
}
}