LeetCode第30题思悟——串联所有单词的子串(substring-with-concatenation-of-all-words)
知识点预告
- HashMap的统计作用;
- 遍历字符串的技巧;
题目要求
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例
示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoor” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的思路
首先,因为words中单词出现的顺序是不定的,为了查找方便,我们需要对words进行排序,排序后即可使用二分查找法检索特定字符串;
然后就在s中,每次截取一个word长度的子串,进行查找;如果从i开始,words中的所有word均出现过,那么i就是一个起始位置;
这个解题思路,“人脑”的痕迹很明显;这说明一个问题,就是还没有以算法的角度来解题,或者说,是没有体现算法意识和算法能力的,即便可以解决问题,也不值得推崇;
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> results=new ArrayList<>();
if(s==null||s.equals("")||words==null||words.length==0){
return results;
}
Arrays.sort(words);
int wordNum=words.length;
int wordLength=words[0].length();
int stepLength=wordNum*wordLength;
int stringLength=s.length();
boolean findTarget;
String checkString;
int startIndex;
int endIndex;
int checkBorder=stringLength-stepLength;
ArrayList<String> firstBuffer=new ArrayList<>();
ArrayList<String> secondBuffer=new ArrayList<>();
ArrayList<String> repetitionChecker=firstBuffer;
ArrayList<String> nextBuffer=secondBuffer;
ArrayList<String> temp;
Collections.addAll(firstBuffer,words);
for(int i=0;i<=checkBorder;i++){
findTarget=true;
for(int j=0;j<wordNum;j++){
startIndex=i+j*wordLength;
endIndex=i+(j+1)*wordLength;
checkString=s.substring(startIndex,endIndex);
if(!containWord(checkString,words,0,wordNum-1)){
findTarget=false;
break;
}else{//重复检查
if(repetitionChecker.contains(checkString)){
nextBuffer.add(checkString);
repetitionChecker.remove(checkString);
}else{
findTarget=false;
break;
}
}
}
if(findTarget){
results.add(i);
temp=repetitionChecker;
repetitionChecker=nextBuffer;
nextBuffer=temp;
}else{
repetitionChecker.clear();
nextBuffer.clear();
Collections.addAll(repetitionChecker,words);
}
}
return results;
}
//二分查找字符串
public boolean containWord(String target,String[] container,int start,int end){
if(start==end){
return target.equals(container[start]);
}
int middle=(start+end)/2;
if(target.equals(container[middle])){
return true;
}
if(before(target,container[middle])){
if(start==middle){
return false;
}else{
if(start<=middle-1) {
return containWord(target, container, start, middle - 1);
}
}
}else{
if(middle==end){
return false;
}else{
if(middle+1<=end) {
return containWord(target, container, middle + 1, end);
}
}
}
return false;
}
public boolean before(String first,String second){
int length=first.length();
char[] firstArray=first.toCharArray();
char[] secondArray=second.toCharArray();
for(int i=0;i<length;i++){
if(firstArray[i]>secondArray[i]){
return false;
}else if(firstArray[i]<secondArray[i]){
return true;
}
}
return false;
}
优秀解法
//解法A
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> ans = new ArrayList<>();
if(s == null || words == null || s.length() == 0 || words.length == 0 )
return ans;
HashMap<String, Integer> hashMap = new HashMap<>();
int numWords = words.length;
int lenS = s.length();
int lenWord = words[0].length();
int lenWords = numWords * lenWord;
for(String word : words) {
hashMap.put(word, hashMap.getOrDefault(word, 0) + 1);
}
for(int i = 0; i + lenWords < lenS + 1; i++ ) {
String temp = s.substring(i, i + lenWords);
HashMap<String, Integer> tempMap = new HashMap<>();
for(int j = 0; j < lenWords; j += lenWord) {
String w = temp.substring(j, j + lenWord);
tempMap.put(w, tempMap.getOrDefault(w, 0) + 1);
}
if(hashMap.equals(tempMap))
ans.add(i);
}
return ans;
}
//解法B
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (words.length == 0 || s.length() == 0) {
return res;
}
int wordLength = words[0].length();
int length = words.length;
if (s.length() < length * wordLength) {
return res;
}
if(wordLength == 0){
for (int i = 0; i <= s.length(); i++) {
res.add(i);
}
return res;
}
///
Map<String,Integer> referee = new HashMap<>();
for (String word : words) {
referee.put(word,referee.getOrDefault(word,0)+1);
}
for (int i = 0; i <= s.length()-length * wordLength; i++) {
int cur = i;
Map<String,Integer> window = new HashMap<>();
for (;cur + wordLength <= s.length(); cur+=wordLength) {
String word = s.substring(cur,cur+wordLength);
window.put(word,window.getOrDefault(word,0)+1);
if(referee.getOrDefault(word,0) < window.get(word)){
break;
}
}
if(cur-i == length * wordLength){
res.add(i);
}
}
return res;
}
//解法C
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
if(s.length()==0 || words.length==0){
return res;
}
int len = words[0].length();
int totallen = len*words.length;
Map<String,Integer> mymap = new HashMap<String,Integer>();
for(int i=0;i<words.length;i++){
mymap.put(words[i], mymap.getOrDefault(words[i], 0)+1);
}
Map<String,Integer> window = new HashMap<String,Integer>();
for(int i=0;i<len;i++){
int l = i;
int r = i;
window.clear();
while(l<=s.length()-totallen && r<=s.length()-len){
String str = s.substring(r,r+len);
if(!mymap.containsKey(str)){
r = r+len;
l = r;
window.clear();
continue;
}
window.put(str, window.getOrDefault(str, 0)+1);
while(mymap.get(str)<window.get(str)){
String str2 = s.substring(l,l+len);
window.put(str2, window.getOrDefault(str2, 0)-1);
l = l+len;
}
r = r+len;
if(r-l == totallen){
res.add(l);
}
}
}
return res;
}
//解法D
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> list = new ArrayList<>();
if (words.length == 0 || s.length() < words.length * words[0].length()) {
return list;
}
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < words.length; i++) {
map.put(words[i], map.getOrDefault(words[i], 0) + 1);
}
int listLen = words.length;
int wordLen = words[0].length();
for (int i = 0; i < wordLen; i++) {
for (int j = i; j <= s.length() - wordLen * listLen; j += wordLen) {
Map<String, Integer> map2 = new HashMap<>();
for (int k = listLen - 1; k >= 0; k--) {
String temp = s.substring(j + k * wordLen, j + (k + 1) * wordLen);
int val = map2.getOrDefault(temp, 0) + 1;
if (val > map.getOrDefault(temp, 0)) {
j += k * wordLen;
break;
}
if (k == 0) {
list.add(j);
} else {
map2.put(temp, val);
}
}
}
}
return list;
}
差异分析
- 我的方法使用排序+二分查找定位words中的word;而优秀解法中则统一使用了HashMap统计word信息;
- 我的方法通过判断字符串是否还存在于list中,来解决重复字符串的问题;优秀解法中使用HashMap的值来解决该问题;所谓重复字符串问题就是指同一个word,多次出现在words中;
- 基础变量的计算没有什么问题;
- for循环的设定有一定的差别:我的方法是从头开始遍历s,每个字符走一遍;解法A则是每次截出一个字串的长度,然后对该子串进行对比;解法B则和我的遍历方法循环一致;解法C和解法D则先对word的长度遍历,很巧妙;因为遍历完一个word后,其余的单词情况均在之前已经处理过,就无须再处理了;这也是C和D效率很高的重要原因;
知识点小结
- HashMap的统计作用;
- 遍历字符串的技巧;