js算法题解(第31天)--leetcode 76. 最小覆盖子串

379 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

前言

每天一道算法题,死磕算法

这是leetcode上的第76道题目76. 最小覆盖子串

题目

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案。  

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

解析

一看这个是子串问题,就要使用一个新的policy---滑动窗口

首先先理解一下什么是滑动窗口?

不知道大家在学网络原理的时候还记得滑动窗口协议么?

滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。

image.png

滑动窗口算法其实和这个是一样的,只是应用场景不一样。

简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作。

什么时候使用滑动窗口算法?

滑动窗口是双指针的一种,是专门用来解决子串问题的。

模板

什么都没有模板呀,模板在手,天下我有

1.先定义两个对象,一个记录窗口中的字符,一个记录需要凑齐的字符

 let window = {},need = {};
 for(let c of t){
   if(need[c]){
     need[c] = 0;
   }
   need[c]++;
 }

2.初始化窗口的两端,并进行滑动,valid是计算当前子串包含的符合条件字符的个数

 let left = 0,right =0,valid = 0;
 // 开始滑动
 while(right<Object.keys(s).length){}

3.窗口右侧开始向右滑动

// 开始滑动
 while(right<Object.keys(s).length){
   let c = s[right];
   right++;
   // 进行一系列数据操作
 }

4.当valid和need的length一致的时候,就说明窗口中包含了所有需要的字符,此时,left开始缩小

 while(valid === Object.keys(need).length){
       const d = s[left];
       left++;
       // 进行一系列数据操作

   }

所以最终我们的框架就是

var slidingWindow = function(s,t){
 let window = {},need = {};
 for(let c of t){
   if(need[c]){
     need[c] = 0;
   }
   need[c]++;
 }
 let left = 0,right =0,valid = 0;
 // 开始滑动
 while(right<Object.keys(s).length){
   let c = s[right];
   right++;
   // 进行一系列数据操作
   while(判断是否需要缩小){
       const d = s[left];
       left++;
       // 进行一系列数据操作

   }
   // 符合条件的时候
  
 }
}

有了模板以后就相当于有了一把利剑

题解

然后再回来看我们的题目,其实就是让我们求出滑动窗口滑动到结束的时候,窗口中的字符串,我们模板只是管滑动,但并没有记录窗口,所以我们定义两个属性start和len,来表明窗口的开始位置和长度

var minWindow = function(s,t){
  let need ={},window={};
  for(let c of t){
    if(!need[c]){
      need[c]=0;
    }
    need[c]++;
  }
  let left=0,right=0,valid=0;
  let start=0,len=Infinity;
  while(right<s.length){
    let c = s[right];
    right++;
    if(need[c]){
      if(!window[c]){
        window[c]=0;
      }
      window[c]++;
      if(window[c]===need[c]){
        valid++;
      }
    }
   
    while(valid === Object.keys(need).length){
      // 在这里更新最小覆盖子串
      if(right-left<len){
        start = left;
        len = right - left;
      }
      // 此时缩小左窗口
      let d = s[left];
      left++;
      if(need[d]){
        if(window[d]===need[d]){
          valid--;
        }
        window[d]--;
      }
    }
  }
  return len === Infinity? "": s.substr(start,len);
}

直接搞定

总结

滑动窗口这个代码的确很长很长,只有理解了原理,才能方便记忆,这是一个把抽象变具体的过程

参考