第3题:无重复字符的最长子串
题目描述
给定一个字符串 s,请你找出其中不含有重复字符的最长子串的长度。
解法:滑动窗口 + 哈希表
/**
* 无重复字符的最长子串
* @param {string} s
* @return {number}
*/
function lengthOfLongestSubstring(s) {
if (!s || s.length === 0) return 0;
// 使用 Map 存储字符及其最后出现的索引
const charIndexMap = new Map();
let maxLength = 0;
let left = 0; // 滑动窗口左边界
for (let right = 0; right < s.length; right++) {
const currentChar = s[right];
// 如果当前字符已经在窗口中出现过
if (charIndexMap.has(currentChar) && charIndexMap.get(currentChar) >= left) {
// 移动左边界到重复字符的下一个位置
left = charIndexMap.get(currentChar) + 1;
}
// 更新字符最新出现的位置
charIndexMap.set(currentChar, right);
// 更新最大长度
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
// 测试用例
console.log(lengthOfLongestSubstring("abcabcbb")); // 输出: 3
console.log(lengthOfLongestSubstring("bbbbb")); // 输出: 1
console.log(lengthOfLongestSubstring("pwwkew")); // 输出: 3
console.log(lengthOfLongestSubstring("")); // 输出: 0
console.log(lengthOfLongestSubstring("abcdef")); // 输出: 6
算法思路
- 滑动窗口:维护一个窗口
[left, right] - 哈希表:记录每个字符最后出现的位置
- 遇到重复:移动左边界到重复字符的下一个位置
- 更新答案:每次扩展右边界时更新最大长度
第76题:最小覆盖子串
题目描述
给你一个字符串 s 和一个字符串 t,返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""。
解法:滑动窗口
/**
* 最小覆盖子串
* @param {string} s
* @param {string} t
* @return {string}
*/
function minWindow(s, t) {
if (!s || !t || s.length < t.length) return "";
// 记录 t 中各字符出现次数
const need = new Map();
const window = new Map();
// 统计 t 中各字符频次
for (let char of t) {
need.set(char, (need.get(char) || 0) + 1);
}
let left = 0, right = 0; // 滑动窗口边界
let valid = 0; // 记录满足 need 条件的字符种类数
// 记录最小覆盖子串的起始索引及长度
let start = 0;
let minLen = Infinity;
while (right < s.length) {
// c 是将移入窗口的字符
const c = s[right];
// 扩大窗口
right++;
// 进行窗口内数据的一系列更新
if (need.has(c)) {
window.set(c, (window.get(c) || 0) + 1);
// 如果窗口中该字符的数量等于需要的数量
if (window.get(c) === need.get(c)) {
valid++;
}
}
// 判断左侧窗口是否要收缩
while (valid === need.size) {
// 更新最小覆盖子串
if (right - left < minLen) {
start = left;
minLen = right - left;
}
// d 是将移出窗口的字符
const d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
if (need.has(d)) {
// 如果移除前刚好满足条件,移除后就不满足了
if (window.get(d) === need.get(d)) {
valid--;
}
window.set(d, window.get(d) - 1);
}
}
}
// 返回最小覆盖子串
return minLen === Infinity ? "" : s.substring(start, start + minLen);
}
// 测试用例
console.log(minWindow("ADOBECODEBANC", "ABC")); // 输出: "BANC"
console.log(minWindow("a", "a")); // 输出: "a"
console.log(minWindow("a", "aa")); // 输出: ""
console.log(minWindow("ab", "b")); // 输出: "b"
console.log(minWindow("abc", "cba")); // 输出: "abc"
算法思路
- 扩展窗口:不断向右扩展,直到包含所有目标字符
- 收缩窗口:一旦满足条件,开始收缩左边界
- 更新答案:在满足条件时记录最优解
- 维护计数:使用两个 Map 维护字符计数和满足条件的种类数
JavaScript 特殊技巧
- 使用
Map而不是普通对象,避免原型链污染 - 使用
substring()获取子串 - 使用
has()方法检查键是否存在
时间复杂度
- 第3题:O(n),其中 n 是字符串长度
- 第76题:O(|s| + |t|),双指针最多各遍历一次
空间复杂度
- 两题都是 O(k),其中 k 是字符集大小