LeetCode第30题思悟——串联所有单词的子串(substring-with-concatenation-of-all-words)

LeetCode第30题思悟——串联所有单词的子串(substring-with-concatenation-of-all-words)

知识点预告

  1. HashMap的统计作用;
  2. 遍历字符串的技巧;

题目要求

给定一个字符串 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;
}

差异分析

  1. 我的方法使用排序+二分查找定位words中的word;而优秀解法中则统一使用了HashMap统计word信息;
  2. 我的方法通过判断字符串是否还存在于list中,来解决重复字符串的问题;优秀解法中使用HashMap的值来解决该问题;所谓重复字符串问题就是指同一个word,多次出现在words中;
  3. 基础变量的计算没有什么问题;
  4. for循环的设定有一定的差别:我的方法是从头开始遍历s,每个字符走一遍;解法A则是每次截出一个字串的长度,然后对该子串进行对比;解法B则和我的遍历方法循环一致;解法C和解法D则先对word的长度遍历,很巧妙;因为遍历完一个word后,其余的单词情况均在之前已经处理过,就无须再处理了;这也是C和D效率很高的重要原因;

知识点小结

  1. HashMap的统计作用;
  2. 遍历字符串的技巧;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值