滑动窗口 --typescript

滑动窗口

例题一 最小覆盖字串

最小覆盖字串

滑动窗口算法的思路:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是「滑动窗口」这个名字的来历。

滑动窗口算法的代码框架
/* 滑动窗口算法框架 */
function slidingWindow(s:string, t:string) {
    // need待匹配计数 window窗口内字符串计数
    let need={},window={}
    for(let i of t) need[i]=need[i]+1 || 1
    
    let [r,l,valid]=[0,0,0]
    while (r < s.length) {
        // c 是将移入窗口的字符
        const c = s[right]
        // 右移窗口
        right++
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        console.log(`window: [${l}, ${r})`)
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            const d = s[left]
            // 左移窗口
            left++
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}
开始套框架前,只需要思考以下四个问题:

1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?

2、什么条件下,窗口应该暂停扩大,开始移动 left 缩小窗口?

3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?

4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

如果一个字符进入窗口,应该增加 window 计数器;如果一个字符将移出窗口的时候,应该减少 window 计数器;当 valid 满足 need 时应该收缩窗口;应该在收缩窗口的时候更新最终结果。

完整代码
function minWindow(s: string, t: string): string {
    let res=""
    // len 记录最小字串的长度
    let [l,r,valid,len]=[0,0,0,Number.MAX_VALUE]
    let need={},window={}
    for(let i of t) need[i]=need[i]+1 || 1
    while(r<s.length){
        if(need[s[r]]){
            // 将字符移入窗口
            window[s[r]]=window[s[r]]+1 || 1
            // 窗口内数据更新
            if(window[s[r]]===need[s[r]]){
                valid++
            }
        }
        // 右移窗口
        r++
        // 判断左窗口是否需要收缩
        while(valid===Object.keys(need).length){
            // 更新最小字串
            if(r-l<len){
                res=s.slice(l,r)
                len=r-l
            }   
            // 窗口内数据更新
            if(need[s[l]]){
                if(window[s[l]]===need[s[l]]){
                    valid--
                }
                // 将字符移出窗口
                window[s[l]]--
            }
            // 右移窗口
            l++
        }
    }
    return res
};

需要注意的是,当我们发现某个字符在 window 的数量满足了 need 的需要,就要更新 valid,表示有一个字符已经满足要求。而且,你能发现,两次对窗口内数据的更新操作是完全对称的。

valid == Object.keys(need).length 时,说明 T 中所有字符已经被覆盖,已经得到一个可行的覆盖子串,现在应该开始收缩窗口了,以便得到「最小覆盖子串」。

移动 left 收缩窗口时,窗口内的字符都是可行解,所以应该在收缩窗口的阶段进行最小覆盖子串的更新,以便从可行解中找到长度最短的最终结果。

例题二 字符串排列

字符串排列

function checkInclusion(s1: string, s2: string): boolean {
  let [l, r, valid] = [0, 0, 0]
  let need = {}, window = {}
  for (let s of s1) need[s] = need[s] + 1 || 1
  while (r < s2.length) {
    if (need[s2[r]]) {
      window[s2[r]] = window[s2[r]] + 1 || 1
      if (window[s2[r]] === need[s2[r]]) {
        valid++
      }
    }
    r++
    while (valid === Object.keys(need).length) {
      if(r-l===s1.length) return true
      if (need[s2[l]]) {
        if (window[s2[l]] === need[s2[l]]) {
          valid--
        }
        window[s2[l]]--
      }
      l++
    }
  }
  return false
};

例题三 找到字符串中所有字母异位词

找到字符串中所有字母异位词

function findAnagrams(s: string, p: string): number[] {
  let res:number[] = []
  let need={},window={}
  for (let i of p) need[i] = need[i] + 1 || 1
  let l:number = 0, r:number = 0
  let valid:number=0
  while (r < s.length) {
    if (need[s[r]]) {
      window[s[r]] = window[s[r]] + 1 || 1
      if (need[s[r]] === window[s[r]]) {
        valid++
      }
    }
    r++
    while (r - l >= p.length) {
      if (valid === Object.keys(need).length) {
        res.push(l)
      }
      if (need[s[l]]) {
        if (window[s[l]] === need[s[l]]) {
          valid--
        }
        window[s[l]]--
      }
      l++
    }
  }
  return res
};
例题四 无重复字符的最长子串

无重复字符的最长子串

function lengthOfLongestSubstring(s: string): number {
  let res: number = 0
  let [r, l] = [0, 0]
  let window = {}
  while (r < s.length) {
    window[s[r]] = window[s[r]] + 1 || 1
    while (window[s[r]] > 1) {
      window[s[l]]--
      l++
    }
    r++
    res=Math.max(res,r-l)
  }
  return res
};

资料来源:labuladong

例题五替换后的最长重复字符

替换后的最长重复字符

function characterReplacement(s: string, k: number): number {
    let [l,r,n,maxLen]=[0,0,s.length,0]
    let window=new Array(26).fill(0)
    while(r<n){
        //统计每个字符出现的次数
        window[s[r].charCodeAt(0)-'A'.charCodeAt(0)]++
        //出现最多的次数
        maxLen=Math.max(maxLen,window[s[r].charCodeAt(0)-'A'.charCodeAt(0)])
        //窗口里的字符长度减去出现最多次数若大于K,则收缩窗口
        if(r-l+1-maxLen>k){
            window[s[l].charCodeAt(0)-'A'.charCodeAt(0)]--
            l++
        }
        r++
    }
    return r-l
};
```### 例题五 [904. 水果成篮](https://leetcode.cn/problems/fruit-into-baskets/)

```typescript
function totalFruit(fruits: number[]): number {
    // 窗口边界
    let [left,right] = [0,0]
    let ans=0
    // 两个篮子
    let ln=fruits[left],rn=fruits[right]
    while(right<fruits.length){
        // 两种类型,扩大窗口,更新数据
        if(fruits[right]===ln||fruits[right]===rn){
            right++
            ans=Math.max(ans,right-left)
        }
        else{
            // 出现第三种,right-1是第二种
            // 此时为了摘最多的果实,ln只能为right-1这种类型
            // 否则只能从right开始摘(这样摘的不是最多)
            left = right-1
            ln = fruits[left]
            // 向前遍历到连续的ln类型的第一个,然后从left这个位置开始摘
            while(left>=1&&fruits[left-1]===ln) left--
            // 将第三种类型记录
            rn=fruits[right]
            // 更新数据
            ans=Math.max(ans,right-left)
        }
    }
    return ans
};

例题六 209. 长度最小的子数组

function minSubArrayLen(target: number, nums: number[]): number {
    // 初始化窗口[left,right) 左闭右开
    let [left,right] = [0,0]
    let ans=Infinity
    let sum=0
    while(right<nums.length){
        // 扩大窗口
        sum+=nums[right]
        right++
        // 窗口内达到条件,开始缩小窗口,并更新状态
        while(sum>=target){
            ans=Math.min(ans,right-left)
            sum-=nums[left]
            left++
        }
    }
    return ans===Infinity?0:ans
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值