子串
[1- 和为K的子数组](#1- 和为K的子数组)
[2- 滑动窗口最大值](#2- 滑动窗口最大值)
[3- 最小覆盖子串](#3- 最小覆盖子串)
1- 和为K的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k**的子数组的个数。
子数组是数组中元素的连续非空序列。
var subarraySum = function(nums, k) {
// "连续子数组" + "求和"-》前缀和 两个前缀和的差必然是一段连续的区间
// 哈希表+哈希表
// 子数组和 = 当前前缀和 - 之前某个时刻的前缀和
let count = 0
let currentSum = 0
let map = new Map()
// 初始化哈希表,处理从数组下标0开始就满足条件的数组
map.set(0,1)
//
for(let num of nums){
currentSum += num // 计算当前前缀和
// 如果 (当前和 - 目标值) 在之前出现过
if(map.has(currentSum-k)){
count += map.get(currentSum-k)
}
// 更新 Map:记录当前前缀和出现的次数
map.set(currentSum,(map.get(currentSum)||0)+1)
}
return count
};
2- 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值。
var maxSlidingWindow = function(nums, k) {
// 单调递减队列:保持单调性;队首即最大;移除过期元素
// 使用数组模拟双端队列
// queue存储索引值,且索引值对应的数组元素值单调递减
let queue = []
let result = []
// 遍历数组的每个元素
for(let i = 0; i< nums.length; i++){
// 1. 维护队列的单调递减性
// 将队列中所有比当前元素值小的元素对应的索引值出栈
while(queue.length > 0 && nums[queue[queue.length-1]] <= nums[i]){
queue.pop()
}
// 2. 当前元素的索引值入队
// 此时队列中剩下的索引值对应的元素值都比当前元素值大 或者是 空队列
queue.push(i)
// 3. 移除窗口外的过期索引
// 窗口的范围是【i-k+1, i】
if(queue[0] <= i - k){
queue.shift()
}
// 4. 记录当前窗口的最大值
if(i >= k-1){
result.push(nums[queue[0]])
}
}
return result
};
3- 最小覆盖子串
给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的 最短窗口 子串 ,使得该子串包含 t 中的每一个字符(包括重复字符 )。如果没有这样的子串,返回空字符串""。
测试用例保证答案唯一。
var minWindow = function(s, t) {
// 滑动窗口+双指针+Object(用时久)
// 初始化:need记录t字符串的字符需求;window记录s字符串的窗口字符
let need = {}
let window = {}
// 记录t中的字符需求
for(let char of t){
need[char] = (need[char] || 0) + 1
}
// 左右双指针模拟滑动窗口,窗口范围[left,right)
let left = 0
let right = 0
// 窗口中满足need数量要求的字符串种类数
let valid = 0
// 记录最小子串的起始位置和长度
let len = Infinity
let start = 0
//移动右指针扩大窗口
while(right < s.length){
// c:当前移入窗口的字符
let c = s[right]
// 右指针右移扩大窗口
right++
// 更新窗口内的数据(仅处理t中存在的字符)
if(need[c]){
// 窗口中该字符数量加1
window[c] = (window[c] || 0) + 1
// 窗口中该字符数量=need中所需数量,该种字符达到要求,valid+1
if(window[c] === need[c]){
valid++
}
}
// 当前窗口满足条件(包含t中的每一个字符)
// 收缩左指针
while(valid === Object.keys(need).length){
// 更新最小覆盖的字串
if(right - left < len){
// 更新起始位置
start = left
// 更新窗口长度,左闭右开直接-
len = right - left
}
// d:当前移除窗口的字符
let d = s[left]
// 左指针右移,收缩窗口
left++
// 移除的字符是need中需要的
if(need[d]){
// 如果之前该字符满足条件,现在移除,不满足条件,valid-1
if(window[d] === need[d]){
valid--
}
// 窗口中该字符数量-1
window[d]--
}
}
}
// 返回
return len === Infinity ? '' : s.substr(start,len)
};
var minWindow = function(s, t) {
// 用ASCII数组,记录t的字符需求
// 索引对应字符ASCII码,值对应需求数量
// fill(0),防止后续++的时候出现undefined+1=nan的错误
let need = Array(128).fill(0)
// 未凑齐的字符种类数(初始是t的字符种类数)
let requiredKinds = 0
for(let char of t){
const code = char.charCodeAt(0) // 转成ASCII码(比如'A'→65,'B'→66)
if(need[code] === 0){
// 首次统计该字符,种类数+1
requiredKinds++
}
// 该字符的需求数量+1
need[code]++
}
let left = 0
// 初始化子串的起始索引值和长度
let start = 0
let len = Infinity
// 寻找子串
for(let right = 0;right < s.length; right++){
// 窗口区间【left,right】
// 窗口中新加入的字符对应的ASCII码
const rChar = s.charCodeAt(right)
// 对应t的需求数量-1
need[rChar]--
// t中该字符的需求数量=0,该窗口中已包含该字符的需求
if(need[rChar] === 0){
// 未凑齐的字符种类数-1
requiredKinds--
}
// 未凑齐的字符种类数=0,窗口已包含t中所有字符
while(requiredKinds === 0){
// 更新最小子串的长度和起始索引值
if(right - left < len){
start = left
len = right - left
}
// 收缩窗口,Lchar:移出窗口的字符对应的ASCII码
const Lchar = s.charCodeAt(left)
// 如果窗口之前完全包含该字符
if(need[Lchar] === 0){
// 现在移除该字符,窗口未凑齐的字符种类数+1
requiredKinds++
}
// 左指针右移,收缩
left++
// need需求中对应+1
need[Lchar]++
}
}
// str.substr(startIndex, length),第二个参数是长度
// 窗口区间是[],len需要+1
return len === Infinity ? '' : s.substr(start,len+1)
};