给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串
的长度。
示例 1:输入: s = "abcabcbb" 输出: 3
解释: 因为无重复字符的最长子串是
"abc"
,所以其长度为 3。示例 2:输入: s = "bbbbb" 输出: 1
解释: 因为无重复字符的最长子串是
"b"
,所以其长度为 1。示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是"wke"
,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke"
是一个子序列,不是子串。
要解决这个问题,我们可以使用滑动窗口的方法。基本思想是使用两个指针表示一个窗口,这个窗口内的字符串就是我们要找的不含有重复字符的子串。随着我们遍历字符串,我们会根据需要移动窗口的起始指针,以确保窗口内的字符串始终不含有重复字符。
以下是解决这个问题的步骤:
初始化:定义两个指针
start
和end
,分别表示子串的起始和结束位置。初始时,两者都指向字符串的开始位置。同时,使用一个哈希表(HashMap
)来存储字符及其索引。遍历字符串:移动
end
指针遍历字符串。对于每个字符,检查它是否已经在哈希表中:
- 如果不在哈希表中,将其添加到哈希表中,并更新字符的索引。
- 如果已经在哈希表中,并且其索引大于或等于
start
,则说明我们找到了一个重复字符。在这种情况下,我们需要移动start
指针,直到重复字符从哈希表中移除。更新最大长度:在每次迭代中,更新不含有重复字符的子串的最大长度。这可以通过比较当前子串的长度(
end - start + 1
)和已知的最大长度来实现。返回结果:遍历完成后,返回不含有重复字符的子串的最大长度。
package leetcode;
import java.util.HashMap;
import java.util.Map;
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
String s = "abcabcbb";
int maxLen = solution.lengthOfLongestSubstring(s);
System.out.println(maxLen);
}
public int lengthOfLongestSubstring(String s) {
int start = 0;
int end = 0;
int maxLen = 0;
Map<Character, Integer> charIndexMap = new HashMap<>();
while (end < s.length()) {
char currentChar = s.charAt(end);
if (charIndexMap.containsKey(currentChar)) {
//如果字符已经存在,并且索引大于等于start,则移动start指针;
if (charIndexMap.get(currentChar) >= start) {
start = charIndexMap.get(currentChar) + 1;
}
}
//更新字符串索引
charIndexMap.put(currentChar, end);
//更新最大长度
maxLen = Math.max(maxLen, end - start + 1);
end++;
}
return maxLen;
}
}
困难
给你一个字符串
s
、一个字符串t
。返回s
中涵盖t
所有字符的最小子串。如果s
中不存在涵盖t
所有字符的子串,则返回空字符串""
。注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。- 如果
s
中存在这样的子串,我们保证它是唯一的答案。示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。示例 2:
输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。示例 3:
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
解决这个问题的思路是使用滑动窗口算法来找到一个字符串
s
中的最小子串,这个子串包含了另一个字符串t
中所有的字符。以下是详细的步骤和思路:
初始化数据结构:
- 创建一个哈希表
tCount
来存储字符串t
中每个字符的出现次数。- 初始化变量
required
为tCount
的大小,表示需要在s
中找到的字符种类数。- 创建另一个哈希表
windowCount
来存储当前窗口中每个字符的出现次数。- 初始化变量
valid
来跟踪当前窗口中满足t
条件的字符数量。滑动窗口:
- 使用两个指针
left
和right
来表示当前窗口的边界。- 从左到右扩展窗口,直到窗口包含了
t
中所有字符。扩展窗口:
- 移动
right
指针来扩展窗口,将s
中的字符加入到windowCount
中。- 如果
windowCount
中的字符计数满足tCount
中的要求(即字符出现次数等于或超过t
中的计数),则增加valid
的值。检查窗口:
- 当窗口包含了
t
中所有字符(valid
等于required
)时,尝试收缩窗口以找到最小覆盖子串。收缩窗口:
- 移动
left
指针来收缩窗口,从windowCount
中减去s
中的字符计数。- 如果
windowCount
中的字符计数低于tCount
中的要求,减少valid
的值。更新最小覆盖子串:
- 在每次可能的窗口收缩后,检查当前窗口的长度是否小于已知的最小长度
minLen
,如果是,则更新minLen
和最小子串的起始位置minStart
。返回结果:
- 继续扩展和收缩窗口,直到
right
指针到达字符串s
的末尾。- 如果找到了最小覆盖子串,返回子串;否则返回空字符串。
这种方法的时间复杂度是 O(N),其中 N 是字符串
s
的长度,因为我们最多只需要遍历s
一次。空间复杂度是 O(K),其中 K 是字符串t
中不同字符的数量,这是因为我们使用哈希表来存储t
中字符的出现次数。
package leetcode;
import java.util.HashMap;
import java.util.Map;
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
String s = "ADOBECODEBANC";
String t = "ABC";
String result = solution.minWindow(s, t);
System.out.println(result); // 应该输出 "BANC"
}
public String minWindow(String s, String t) {
if (s.isEmpty() || t.isEmpty()) return "";
Map<Character, Integer> tCount = new HashMap<>();
for (char c : t.toCharArray()) {
tCount.put(c, tCount.getOrDefault(c, 0) + 1);
}
int required = tCount.size(); // 需要的字符数量
Map<Character, Integer> windowCount = new HashMap<>();
int left = 0, right = 0;
int minLen = Integer.MAX_VALUE, minStart = 0;
int valid = 0; // 窗口中满足要求的字符数量
while (right < s.length()) {
char c = s.charAt(right);
windowCount.put(c, windowCount.getOrDefault(c, 0) + 1);
if (tCount.containsKey(c) && windowCount.get(c).intValue() == tCount.get(c)) {
valid++;
}
while (left <= right && valid == required) {
char startChar = s.charAt(left);
if (minLen > right - left + 1) {
minLen = right - left + 1;
minStart = left;
}
windowCount.put(startChar, windowCount.get(startChar) - 1);
if (tCount.containsKey(startChar) && windowCount.get(startChar).intValue() < tCount.get(startChar)) {
valid--;
}
left++;
}
right++;
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen);
}
}