目录
[13. 水果成篮(904. Fruit Into Baskets)](#13. 水果成篮(904. Fruit Into Baskets))
[14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String)](#14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String))
[15. 串联所有单词的子串(30. Substring with Concatenation of All Words)](#15. 串联所有单词的子串(30. Substring with Concatenation of All Words))
[16. 最小覆盖子串(76. Minimum Window Substring)](#16. 最小覆盖子串(76. Minimum Window Substring))
13. 水果成篮(904. Fruit Into Baskets)
题目链接 :LeetCode 904
详细解题思路:
核心思想:滑动窗口,维护窗口内水果种类≤2
问题转化:求最长子数组,其中不同数字个数≤2
具体步骤:
使用哈希表
hash记录窗口内每种水果的数量变量
kinds记录窗口内水果种类数右指针扩展窗口:
- 如果新水果在窗口中第一次出现,
kinds++当
kinds > 2时,收缩窗口:
左指针水果数量减1
如果减到0,
kinds--更新最大窗口长度
优化:可以用数组代替哈希表(水果种类有限)
时间复杂度:O(n),空间复杂度:O(水果种类数)
java
class Solution {
public int totalFruit(int[] fruits) {
// 水果种类有限,用数组代替哈希表
int[] basket = new int[fruits.length + 1]; // 假设最大下标
int kinds = 0; // 当前篮子中水果种类数
int left = 0, right = 0;
int maxFruits = 0;
while (right < fruits.length) {
// 右指针水果进入篮子
int fruit = fruits[right];
if (basket[fruit] == 0) {
kinds++; // 新种类水果
}
basket[fruit]++;
// 如果种类超过2,收缩窗口
while (kinds > 2) {
int outFruit = fruits[left];
basket[outFruit]--;
if (basket[outFruit] == 0) {
kinds--; // 该种类水果已全部移除
}
left++;
}
// 更新最大水果数
maxFruits = Math.max(maxFruits, right - left + 1);
// 右指针右移
right++;
}
return maxFruits;
}
}
14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String)
题目链接 :LeetCode 438
详细解题思路:
核心思想:固定大小的滑动窗口 + 哈希表统计
具体步骤:
统计p中每个字符的出现次数
pCount维护滑动窗口内字符的出现次数
windowCount维护
valid变量表示窗口中满足条件的字符数窗口大小固定为p的长度
右指针扩展,更新
windowCount和valid当窗口大小等于p长度时:
如果
valid == p.length(),找到异位词左指针收缩,更新
windowCount和valid时间复杂度:O(n),空间复杂度:O(字符集大小)
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (s.length() < p.length()) return result;
// 统计p中字符出现次数
int[] pCount = new int[26];
for (char c : p.toCharArray()) {
pCount[c - 'a']++;
}
// 窗口内字符统计
int[] windowCount = new int[26];
int left = 0, right = 0;
int valid = 0; // 窗口中满足条件的字符数
while (right < s.length()) {
// 右指针字符进入窗口
char inChar = s.charAt(right);
int idx = inChar - 'a';
// 更新窗口统计
windowCount[idx]++;
// 如果该字符在p中,且窗口中出现次数不超过p中次数,valid增加
if (windowCount[idx] <= pCount[idx]) {
valid++;
}
// 当窗口大小等于p长度时
if (right - left + 1 == p.length()) {
// 如果valid等于p长度,说明找到异位词
if (valid == p.length()) {
result.add(left);
}
// 左指针字符移出窗口
char outChar = s.charAt(left);
int outIdx = outChar - 'a';
// 更新窗口统计
if (windowCount[outIdx] <= pCount[outIdx]) {
valid--;
}
windowCount[outIdx]--;
// 左指针右移
left++;
}
// 右指针右移
right++;
}
return result;
}
}
15. 串联所有单词的子串(30. Substring with Concatenation of All Words)
题目链接 :LeetCode 30
详细解题思路:
核心思想:将单词视为字符,转化为"字母异位词"问题
具体步骤:
统计words中每个单词的出现次数
wordCount单词长度固定为
wordLen,总长度totalLen = wordLen * words.length因为单词可能从s的不同位置开始,需要遍历
wordLen个起始位置对于每个起始位置,使用滑动窗口:
每次取
wordLen长度的子串作为一个"字符"维护窗口内单词的出现次数
windowCount当窗口单词数等于words长度时,检查是否匹配
时间复杂度:O(n * wordLen),空间复杂度:O(words.length)
java
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 wordLen = words[0].length();
int totalWords = words.length;
int totalLen = wordLen * totalWords;
// 统计words中单词出现次数
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 尝试所有可能的起始位置(0到wordLen-1)
for (int start = 0; start < wordLen; start++) {
int left = start;
int right = start;
Map<String, Integer> windowCount = new HashMap<>();
int matched = 0; // 已匹配的单词数
while (right + wordLen <= s.length()) {
// 取当前单词
String word = s.substring(right, right + wordLen);
right += wordLen;
// 如果单词在words中
if (wordCount.containsKey(word)) {
windowCount.put(word, windowCount.getOrDefault(word, 0) + 1);
matched++;
// 如果当前单词出现次数超过所需,收缩窗口
while (windowCount.get(word) > wordCount.get(word)) {
String leftWord = s.substring(left, left + wordLen);
windowCount.put(leftWord, windowCount.get(leftWord) - 1);
matched--;
left += wordLen;
}
// 如果匹配了所有单词
if (matched == totalWords) {
result.add(left);
}
} else {
// 如果单词不在words中,重置窗口
windowCount.clear();
matched = 0;
left = right;
}
}
}
return result;
}
}
16. 最小覆盖子串(76. Minimum Window Substring)
题目链接 :LeetCode 76
详细解题思路:
核心思想:滑动窗口 + 双哈希表
具体步骤:
统计t中每个字符的出现次数
need维护窗口内字符出现次数
window维护
valid表示窗口中满足t条件的字符数右指针扩展窗口:
- 更新
window和valid当
valid == need.size()时(窗口中包含t的所有字符):
更新最小子串
左指针收缩窗口,直到
valid < need.size()时间复杂度:O(n),空间复杂度:O(字符集大小)
java
class Solution {
public String minWindow(String s, String t) {
if (s == null || t == null || s.length() < t.length()) {
return "";
}
// 统计t中字符出现次数
Map<Character, Integer> need = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
// 窗口统计
Map<Character, Integer> window = new HashMap<>();
int left = 0, right = 0;
int valid = 0; // 窗口中满足need条件的字符数
int start = 0; // 最小子串起始位置
int minLen = Integer.MAX_VALUE; // 最小子串长度
while (right < s.length()) {
// 右指针字符进入窗口
char inChar = s.charAt(right);
right++;
// 更新窗口统计
if (need.containsKey(inChar)) {
window.put(inChar, window.getOrDefault(inChar, 0) + 1);
if (window.get(inChar).equals(need.get(inChar))) {
valid++;
}
}
// 当窗口中包含t的所有字符时
while (valid == need.size()) {
// 更新最小子串
if (right - left < minLen) {
start = left;
minLen = right - left;
}
// 左指针字符移出窗口
char outChar = s.charAt(left);
left++;
// 更新窗口统计
if (need.containsKey(outChar)) {
if (window.get(outChar).equals(need.get(outChar))) {
valid--;
}
window.put(outChar, window.get(outChar) - 1);
}
}
}
// 返回结果
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}