题目:
给定一个字符串 s
和一个字符串数组 words
。 words
中所有字符串 长度相同。
s
中的 串联子串 是指一个包含 words
中所有字符串以任意顺序排列连接起来的子串。
- 例如,如果
words = ["ab","cd","ef"]
, 那么"abcdef"
,"abefcd"
,"cdabef"
,"cdefab"
,"efabcd"
, 和"efcdab"
都是串联子串。"acdbef"
不是串联子串,因为他不是任何words
排列的连接。
返回所有串联子串在 s
中的开始索引。你可以以 任意顺序 返回答案。
在这道题中,你需要在一个字符串中找到所有子串,这些子串由一组单词的串联组成,每个单词的长度相同。下面是一个 Java 的解题示例,带有详细的注释:
import java.util.*;
public class Solution {
public List<Integer> findSubstring(String s, String[] words) {
// 初始化返回结果列表
List<Integer> result = new ArrayList<>();
// 检查输入字符串和单词数组是否为空
if (s == null || s.length() == 0 || words == null || words.length == 0) {
return result; // 如果任何一个为空,返回空结果
}
// 每个单词的长度
int wordLength = words[0].length();
// 单词的总数量
int wordCount = words.length;
// 所有单词连接后的总长度
int totalLength = wordLength * wordCount;
// 如果字符串长度小于所有单词的总长度,直接返回结果
if (s.length() < totalLength) {
return result;
}
// 统计每个单词出现的次数
Map<String, Integer> wordMap = new HashMap<>();
for (String word : words) {
wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);
}
// 遍历起始点,最多 wordLength 次,以处理不同的偏移量
for (int i = 0; i < wordLength; i++) {
// 初始化滑动窗口
int left = i; // 左指针,初始化为当前偏移量
int count = 0; // 当前窗口中匹配的单词数量
Map<String, Integer> seenWords = new HashMap<>(); // 当前窗口内的单词计数
// 从起始点开始,每次移动一个单词长度
for (int j = i; j <= s.length() - wordLength; j += wordLength) {
// 提取当前单词
String word = s.substring(j, j + wordLength);
// 检查单词是否在字典中
if (wordMap.containsKey(word)) {
// 更新当前窗口内的单词出现次数
seenWords.put(word, seenWords.getOrDefault(word, 0) + 1);
count++; // 增加匹配到的单词数量
// 如果当前单词出现次数超过需要的次数
while (seenWords.get(word) > wordMap.get(word)) {
// 移动左指针,缩小窗口
String leftWord = s.substring(left, left + wordLength);
seenWords.put(leftWord, seenWords.get(leftWord) - 1);
count--; // 减少匹配的单词数量
left += wordLength; // 移动左指针
}
// 如果窗口内的单词数与 wordCount 相同,记录结果
if (count == wordCount) {
result.add(left);
}
} else {
// 如果遇到不在字典中的单词,重置窗口
seenWords.clear();
count = 0;
left = j + wordLength; // 移动左指针到当前单词后
}
}
}
return result; // 返回所有符合条件的起始索引
}
}
详细说明:
-
输入检查:首先检查输入字符串和单词数组是否有效。如果无效,立即返回。
-
初始化变量:
wordLength
:每个单词的长度。wordCount
:单词的总数。totalLength
:所有单词连接后的总长度。
-
单词计数:使用
HashMap
记录每个单词在数组中的出现次数。 -
多次起始点遍历:从 0 到
wordLength - 1
共进行wordLength
次遍历,以处理不同的偏移量。 -
滑动窗口技巧:使用
left
和j
指针来动态维护一个窗口,seenWords
用来记录窗口内的单词出现次数。 -
窗口调整:如果窗口内某个单词出现次数超过需要,则通过移动
left
指针来缩小窗口。 -
结果记录:如果窗口内的单词数与单词数组中的单词数相同,则将当前窗口的起始位置
left
加入结果。
这种方法通过更高效地管理窗口的内容来减少重复计算,提高了解题效率。