30. 串联所有单词的子串
给定一个字符串 s
和一些 长度相同 的单词 words
**。**找出 s
中恰好可以由 words
中所有单词串联形成的子串的起始位置。
注意子串要与 words
中的单词完全匹配,中间不能有其他字符 ,但不需要考虑 words
中单词串联的顺序。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
提示:
1 <= s.length <= 104
s
由小写英文字母组成1 <= words.length <= 5000
1 <= words[i].length <= 30
words[i]
由小写英文字母组成
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
// list:返回的结果集
List<Integer> list = new ArrayList<Integer>();
// len:单词的固定长度
// size:words数组的长度,根据这个长度去创建result数组。
// sign:记录当前出现的单词的顺序,它是第几个出现的。
// repeatNum:之前出现的第几个单词,在现在又出现了。
// 计算result数组的和,就不用去循环遍历看看是否都齐全了。
// repeatIndex:重复的下标
int len = words[0].length(), size = words.length, sign = 0, repeatNum = 0, sum = 0, repeatIndex = 0;
// result数组:下标和words数组下标一一对应,代表对应下标的单词是第几个出现的。
// 如果全都出现了,则全为以,那么和就是len。
// 如果有某个位置大于1,说明出现了重复。
int[] result = new int[size];
// appear:是否出现了words数组中的单词,repeat:是否出现了重复的单词。
boolean appear, repeat;
// i是分类,将一个words数组分为len条路取走。
for(int i = 0; i < len; ++i){
// 如果每个单词长度为3,那么就应该是以下的样子
// 0->3->6->9
// 1->4->7->10
// 2->5->8->11
// 因为j + len取的是开区间,所以要有等号
for(int j = 0 + i; j + len <= s.length(); j = j + len){
String word = s.substring(j, j + len);
appear = false;
repeat = false;
// 从words数组中找到对应的单词下标
for(int m = 0; m < size; ++m){
// 找到了对应的词语,但是这里存在麻烦,假如words中有两个一样的单词,就会出现都堆积到第一个单词的下标上
if(words[m].equals(word)){
int k = -1;
// 如果出现了重复,即代表之前已经在这个下标的位置上加过数字了。
// 那我就应该记录下result[m],这样子我就知道第几个出现的数字出现了重复。
// 接下来在后续操作中,小于这个单词出场顺序的直接归零,其他的减去当前的单词顺序即可。
if(result[m] > 0){
repeat = true;
repeatNum = result[m];
repeatIndex = m;
// 所以需要手动往后面去查询,如果有的话就撤销重复,否则就会提示重复。
for(k = 0; k < size; ++k){
if(words[k].equals(word)){
// 不能写作repeatIndex = repeatNum < result[k] ? m : k;因为当出现了三个及以上的重复时,就会出现异常
repeatIndex = repeatNum < result[k] ? repeatIndex : k;
repeatNum = repeatNum < result[k] ? repeatNum : result[k];
if(result[k] == 0) {
repeat = false;
break;
}
}
}
// 说明到了结尾都没有找到新的对应字符存放的位置,那就是出现了重复。要修改的下标就是所有的重复字符中值最小的那个的下标。
if(k == size){
result[repeatIndex] = sign + 1;
}
}
// 即便是重复也需要修改出场顺序,这样子方便后面操作。
++sign;
// k == -1,根本就没有进入重复。
// k != -1 && k != size 说明进入了重复,但是其中找到了新的空格填充,空格的下标就是k。
if(k == -1){
result[m] = sign;
}
if(k != -1 && k != size){
result[k] = sign;
}
// 不论出没出现重复都可以先加sum
++sum;
appear = true;
break;
}
}
// 说明当前的单词没有在words数组中出现过,直接从下一个单词重新查询
if(!appear){
sign = 0;
sum = 0;
for(int n = 0; n < size; ++n){
result[n] = 0;
}
}
// 说明单词出现了重复
if(repeat){
sign = sign - repeatNum;
sum = sum - repeatNum;
for(int n = 0; n < size; ++n){
if(result[n] > repeatNum){
result[n] = result[n] - repeatNum;
} else {
result[n] = 0;
}
}
}
// 如果sum等于size了,说明已经齐全了
if(sum == size){
// 因为当前的单词是最后一个出现的,所以要获取出现的第一个下标,只要减去((总单词数 - 1) * 固定长度)
list.add(j - len * (size - 1));
// 因为全到齐了,所以检测下一个单词的时候,只要把第一个出现的单词抹去就行了。
// 即原来是[5,4,3,2,1]的改变为[4,3,2,1,0]
--sign;
--sum;
for(int n = 0; n < size; ++n){
if(result[n] > 1){
result[n] = result[n] - 1;
} else {
result[n] = 0;
}
}
}
}
sign = 0;
sum = 0;
for(int n = 0; n < size; ++n){
result[n] = 0;
}
}
return list;
}
}