刷题笔记--串联所有单词的子串
题目:
1、我的写法(超时)
从题面自然想到先用回溯算法把words的全排列先算出来,然后遍历字符串s一次将符合条件的位置加入结果
全排列计算所有可能字符串算法写法:
这是一个模板用于所有全排列算法的情况,本质思想是回溯。四个参数:words代表候选单词数组,path代表已经选过的路径,used代表当前元素有没有使用过,result代表结果这里用了set去重
在每次回溯开始前先判断,当前路径有没有包含所有候选单词,如果都包含了的话就加入结果。
从当前path开始算,尝试所有可能性,把没用到的元素加入path递归,然后恢复数据开始下一次回溯
这是我在外层调用的方法,最后显示超时了,超时原因在于当words长度过长时,递归回溯的方法复杂度不好控制,过于耗时
完整代码:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
//全排列找出words能拼接成的所有字符串
Set<String> concatStr = new HashSet<>();
boolean[] used = new boolean[words.length];
backTrack(words, new ArrayList<>(), used, concatStr);
//遍历字符串s,与所有可能的拼接结果对比,找到符合的结果
int wordLen = 0;
List<Integer> result = new ArrayList<>();
for(int i = 0; i < words.length; i++) {
wordLen += words[i].length();
}
for(int i = 0; i <= s.length() - wordLen; i++) {
if(concatStr.contains(s.substring(i, i + wordLen))) {
result.add(i);
}
}
return result;
}
private void backTrack(String[] words, List<String> path, boolean[] used, Set<String> result) {
if(path.size() == words.length) {
result.add(String.join("", path));
}
for(int i = 0; i < words.length; i++) {
if(used[i] == false) {
//如果没有被用过加入path中
used[i] = true;
path.add(words[i]);
//递归回溯
backTrack(words, path, used, result);
//撤销操作,去掉最后一个元素并标记为未使用
used[i] = false;
path.remove(path.size() - 1);
}
}
}
}
2、官方解法
不需要递归回溯,用到了异位字符串思想
官解的思路类似于找到字符串中的字母异位词,只不过它从之前的字母异位变成了字符串异位。注意,体面中words每个单词的长度都是相等的,所以我们是可以等长的去划分子串的,这样划分之后用哈希表differ去划分。
遍历一遍数组,从0开始,直到最后无法满足长度为止。每次循环,都初始化一个哈希表用来记录划分后的单词出现频率和words的误差,等同于之前字符串中找到字母异位词的做法,只不过这里不再是字母而是一个个长度相等的字符串
完整代码:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<Integer>();
int m = words.length, n = words[0].length(), ls = s.length();
for (int i = 0; i < n; i++) {
if (i + m * n > ls) {
break;
}
Map<String, Integer> differ = new HashMap<String, Integer>();
for (int j = 0; j < m; j++) {
String word = s.substring(i + j * n, i + (j + 1) * n);
differ.put(word, differ.getOrDefault(word, 0) + 1);
}
for (String word : words) {
differ.put(word, differ.getOrDefault(word, 0) - 1);
if (differ.get(word) == 0) {
differ.remove(word);
}
}
for (int start = i; start < ls - m * n + 1; start += n) {
if (start != i) {
String word = s.substring(start + (m - 1) * n, start + m * n);
differ.put(word, differ.getOrDefault(word, 0) + 1);
if (differ.get(word) == 0) {
differ.remove(word);
}
word = s.substring(start - n, start);
differ.put(word, differ.getOrDefault(word, 0) - 1);
if (differ.get(word) == 0) {
differ.remove(word);
}
}
if (differ.isEmpty()) {
res.add(start);
}
}
}
return res;
}
}