题目:
给定一个字符串 s 和一些长度相同的单词 words。在 s 中找出可以恰好串联 words 中所有单词的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出: [0,9]
解释: 从索引 0 和 9 开始的子串分别是 "barfoor" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = "wordgoodstudentgoodword",
words = ["word","student"]
输出: []
然后是输入输出:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
}
}
注意到的关键点:
1.在模式串words里所有的字符串长度是相等的。
2.若是s的长度小于所有word加在一起的长度,则不可能匹配成功。
3.因为返回值是list,所以要持续查找,而不是找到就立即return。
方案初步设计
首先,将各个单词排列组合,拼接得出字符串tmp,再去拿tmp和匹配串s去做匹配的方法肯定是不行的,因为光是组合这一步,复杂度就是Sita(n!)了,这太可怕了。
1.首先可以找到所有单词在s的开始位置的list,然后将所有的开始位置排序。
2.第二步,进行字符组的匹配,匹配方法是:
假如输入是:
s = "barfoothefoobarman",
words = ["foo","bar"]
那么对foo查找,搜索结果是[3,9],对bar查找,搜索结果是[0,12]。然后将list排序,得出[0,3,9,12]。而现在知道word的长度是3,那么0和3可以拼一个结果,9和12可以拼一个结果。那么结果就是[0,9]。
方案进一步研究
我在提交的时候,发现了这样的测试用例:
s = "aaaaaaaaa",
words = ["aaa","aaa"]
就是words里可能有重复的字符串,这时候,除了list之外,还应该设计一个HashMap<String,Integer> wordMap,其key是相应的字符串,其value是在words里有多少个重复,比如现在的结果就是wordMap={‘aaa’:2}。
在这个基础上对第一个’aaa’进行查找,结果是list=[0,1,2,3,4,5,6],然后在这里会发现,第二个是不用再找的,在写程序的时候可以用containsKey()来判定一下即可。
然后再对这个list判断,在这里会再需要另一个HashMap,其结构是HashMap<Integer,String> intMap,其key是s的字符的位置,其value值是以这个位置开始,存在着哪个word。比如{0:‘aaa’},表示在s的第0位上存在者’aaa’这个word。
这样就判断,从list的0开始,从intMap发现0存在着‘aaa’,则wordMap变成[‘aaa’:1],然后list往后走3位,list里存在3,而在intMap里发现3是’aaa’的开始,则wordMap变成[‘aaa’:0]。然后发现所有的word都用上了,则匹配成功了,然后以此类推做其他操作即可。
到此为止,方案的通用性全面得到解决。
代码如下:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> list=new ArrayList<Integer>();//保存有单词的位置
Map<String, Integer> wordMap = new HashMap<>();// 存储 word中各个单词的个数
Map<Integer,String> intMap = new HashMap<>();// 存储 位置上保存了什么单词
if(words.length<=0){
return list;
}
int len=words[0].length();
int count=words.length;
int now_list;
if(s.length()<count*len){
return list;
}
for(int i=0;i<words.length;i++){
if(words[i].length()!=len){
return list;
}
}//以上是处理异常
for (String word : words) {
if (wordMap.containsKey(word)) {
wordMap.put(word, wordMap.get(word) + 1);
continue;
}
now_list=list.size();
wordMap.put(word, 1);
find(list,intMap,s,word);
if(list.size()==now_list){
return new ArrayList<Integer>();//若是有一个单词没找到,则直接宣布不可能
}
}
Collections.sort(list);//将找到的所有位置排序
int j=0;//list的下标
int k=0;//往后的位移
Map<String, Integer> tmpMap = new HashMap<>();// 用于深拷贝,以免破坏数据
List<Integer> res=new ArrayList<Integer>();//最终的结果
for(j=0;j<list.size();j++){
int tmp=list.get(j);
if(tmp<(s.length()-len*count+1)){
tmpMap.putAll(wordMap);//深拷贝
if(check(list,intMap,tmpMap,len,count,tmp)){
res.add(tmp);
}
}else{
break;//到了这个位置,长度就不够了
}
}
return res;
}
public boolean check(List<Integer> list,Map<Integer,String> intMap,Map<String, Integer> wordMap,int len,int count,int start){//用来判断是不是可以组成匹配字符串
boolean res=true;
String word="";
int num=0;
int i=0;
for(i=0;i<count;i++){
if(intMap.containsKey(start+i*len)){
word=intMap.get(start+i*len);//得到对应的单词
}else{
return false;
}
if(wordMap.containsKey(word)&&wordMap.get(word)>0){
wordMap.put(word, wordMap.get(word) -1);
}else{
return false;
}
}
if(i<count-1){
res=false;
}
return res;
}
public void find(List<Integer> list,Map<Integer,String> intMap,String s,String word){
int i=0,j=0;
for(i=0;i<(s.length()-word.length()+1);i++){
for(j=0;j<word.length();j++){
if(s.charAt(i+j)!=word.charAt(j)){
break;
}
}
if(j==word.length()){
list.add(i);
intMap.put(i,word);
}
}
}
}
运行结果:
结果比较令人失望,我第一时间想到的是,可能是因为我的find函数是用的朴素法,所以才会这么慢,然后我找了一下字符串匹配的经典算法。对应Sunday算法的思想,写了个Sunday函数,代码如下:
public void Sunday(List<Integer> list,Map<Integer,String> intMap,String s,String word){
Map<Character, Integer> charMap = new HashMap<>();// 存储 word中各个字符
for(int k=0;k<word.length();k++){//首先创建一个哈希表用来存word里的所有字符,以及其出现的最后一个位置
charMap.put(word.charAt(k),k);
}
int end=word.length()-1,count=0,tmp=0,len=word.length()-1;
while(end<s.length()){
tmp=end;
len=word.length()-1;
count=0;//计数器
while(len>=0&&word.charAt(len)==s.charAt(tmp)){
tmp--;
len--;
count++;
}
if(count==word.length()){
list.add(tmp+1);
intMap.put(tmp+1,word);
}
end++;//向后找一位
while(end<s.length()&&!charMap.containsKey(s.charAt(end))){
end++;
}
if(end==s.length()){
break;
}
end=end+(word.length()-charMap.get(s.charAt(end)))-1;//向后移动
}
}
然后将上面的find替换为Sunday,发现效果也不是特别明显,可能我的List处理逻辑还需要优化,这次就先写个文档记录一下。
参考链接:
1.4种字符串匹配算法
2.几个高效的字符串匹配算法
3.Sunday算法—简单高效的字符串匹配算法