本文始发于个人公众号:TechFlow,原创不易,求个关注

今天是LeetCode专题的第45篇文章,我们一起来看看LeetCode的76题,最小窗口子串Minimum Window Substring。

这题的官方难度是Hard,通过了也是34.2%,4202人点赞,299人反对。从通过率以及点赞比来看,这题的质量很高,稍稍有些偏难,所以小伙伴们请做好准备,这是一道有点挑战的问题。

题意和样例

我们一起来看下题意,这题的题意很短,给定两个字符串S和T。要求设计一个复杂度为的算法,在S串当中找到一个子串,能够包含T串当中的所有字符。要求返回合法且长度最小的窗口的内容。

注意:

  • 如果不存在这样的窗口,返回“”。
  • 如果窗口存在,题目保证有且只有一个。

样例:

Input: S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"

分析

我们来分析一下这个问题,从题意当中大家应该都能感受到它的难度。因为上来题目当中就限定了我们使用的算法的复杂度必须是,然而我们遍历字符串的复杂度就已经是了,也就是说我们不能引入额外的计算开销,否则一定不满足题目的要求。

可能有些同学会想到传说中在时间内判断字符串匹配的KMP算法,如果你不知道这个算法也没有关系,因为这个算法并不适用。因为我们要找的不是完全相等的子串的位置,而是找的是字符构成一样的子串,所以并不能通过引入字符串匹配算法来解决。没有学过KMP算法的同学可以松一口气了,这题当中并不会引入新的算法。

解题的套路

一般来说当我们面临一个算法问题的时候,我们常常的思考过程主要有两种。一种是适配,说白了就是把可能可以用上的算法往问题上套。根据题意先感觉一下,大概会用到什么样的算法,然后详细地推导适配的过程,看看是不是真的适用或者是有什么坑,或者是会出现什么新的问题。如果一切OK,能够推理得通,那么这个算法就是解。第二种方法是建模,也就是说从题意入手,对题意进行深入的分析,对问题进行建模和抽象,找到问题的核心,从而推导出用什么样的算法可以解决。

举个很简单的例子,一般来说我们的动态规划算法都是适配。都是我们先感觉或者是猜测出可以使用动态规划,然后再去找状态和转移,最后建立状态转移方程。而一些搜索问题一般是建模,我们先对问题进行分析,然后找出需要搜索的解的存在空间,然后设计算法去搜索和剪枝,最后找到答案。

据说一些顶级高手这两种方法是一起使用的,所以才可以那么快速地找到解。当然我不是顶级高手,所以这个也只是我的猜测。这个思考过程非常有用,特别是当我们面试的时候,遇到一个从未见过的问题,如果你什么套路也没有,头脑一片空白或者是苦思冥想不得要领是很常见的事情。当你有了套路之后,你就可以试着慢慢找到答案了。

回到这道题本身,我们刚才已经试过了,拿字符串匹配的算法网上套是不行的。在视野里似乎也没有其他的算法可以套用,所以我们换一种思路,试试看建模。

首先我们可以肯定一点,我们需要在遍历的时候找到答案,这样才可以保证算法的复杂度是。我们的目标是寻找子串,也就是说我们遍历的过程应该对应一个子串,并且我们有方法可以快速判断这个子串是否合法。这样我们才可以做到遍历的同时判断答案的可行性。进而可以想到这是一个区间维护的问题,区间维护我们经常使用的方法就是two pointers。所以我们可以试试two pointers能否适用。

实际上这道题的正解就是two pointers。

题解

我们维护了一个区间,我们需要判断区间里的字符构成,这个很容易想到可以使用dict,维护每一个字符出现的次数。在这个题目当中,我们只需要考虑覆盖的情况,也就是说字符多了并不会构成非法。所以我们可以维护一个dict,每次读入一个字符更新它,当dict当中的字符满足要求的时候,为了使得区间长度尽量短,我们可以试着移动区间的左侧,尽量缩短区间的长度。

从区间维护的角度来说,我们每次移动区间右侧一个单位,只有当区间内已经满足题意的时候才会移动左侧。通过移动左侧弹出元素来获取能够满足题意的最佳区间。

我们来看下主要的流程代码:

# 存储区间内的字符
segement = {}
for i in range(n):
segement[s[i]] += 1
# 当满足条件的时候移动区间左侧
while l <= i and satisified(segment):
# 更新最佳答案
if i - l + 1 < ans_len:
ans_len = i - l + 1
beg, end = l, i + 1
# 弹出元素
segement[s[l]] -= 1
l += 1

到这里还有一个小问题,就是怎么样判断这个segment是否合法呢?我们可以用一个数字matched来记录目前已经匹配上的字符的数量。当某个字符在segment当中出现的次数和T中的次数相等的时候,matched加一。当matched的数量和T中字符种类的数量相等的时候,就可以认为已经合法了。

我们把所有的逻辑串起来,就可以通过这题了。

class Solution:
def minWindow(self, s: str, t: str) -> str:
from collections import Counter, defaultdict
# 通过Counter直接获取T当中的字符构成
counter = Counter(t)
n, m = len(s), len(counter)
l, beg, end = 0, 0, 0
cur = defaultdict(int)
matched = 0
flag = False
# 记录合法的字符串的长度
ans_len = 0x3f3f3f3f for i in range(n):
if s[i] not in counter:
continue cur[s[i]] += 1
# 当数量匹配上的时候,matched+1
if cur[s[i]] == counter[s[i]]:
matched += 1 # 如果已经找到了合法的区间,尝试缩短区间的长度
while l <= i and matched == m:
if i - l + 1 < ans_len:
flag = True
beg, end = l, i+1
ans_len = i - l + 1 # 弹出左侧元素
c = s[l]
if c in counter:
cur[c] -= 1
if cur[c] < counter[c]:
matched -= 1 l += 1 return "" if not flag else s[beg: end]

总结

到这里,这道题就算是解决了。很多同学可能会觉得疑惑,为什么我们用到了两重循环,但是它依然还是的算法呢?

这个是two pointers算法的常见问题,也是老生常谈的话题了。我们在分析复杂度的时候,不能只简单地看用到了几层循环,而是要抓住计算的核心。比如在这个问题当中,我们内部的while循环针对的变量是l,l这个变量对于i整体是递增的。也就是说无论外面这个循环执行多少次,里面的这个while循环一共最多累加只能执行n次。那么,当然这是一个的算法。

这题总体来说有些难度,特别是一开始的时候可能会觉得没有头绪无从下手。这个时候有一个清晰的头脑以及靠谱的思考链非常重要,希望大家都能学到这个其中思维的过程,这样以后才可以应付更多的算法问题。

如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。

本文使用 mdnice 排版

LeetCode 76,一题教会你面试算法时的思考套路的更多相关文章

  1. LeetCode 第70题动态规划算法

    导言 看了 动态规划(https://www.cnblogs.com/fivestudy/p/11855853.html)的帖子,觉得写的很好,记录下来. 动态规划问题一直是算法面试当中的重点和难点, ...

  2. python经典面试算法题1.4:如何对链表进行重新排序

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.4 对链表按照如下要求重新排序 [微软笔试题] 难度系数: ...

  3. python经典面试算法题1.3:如何计算两个单链表所代表的数之和

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.2 如何实现链表的逆序 [华为笔试题] 难度系数:⭐⭐⭐ ...

  4. python经典面试算法题1.2:如何从无序链表中移除重复项

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.2 如何实现链表的逆序 [蚂蚁金服面试题] 难度系数:⭐⭐ ...

  5. python经典面试算法题4.1:如何找出数组中唯一的重复元素

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. [百度面试题] 难度系数:⭐⭐⭐ 考察频率:⭐⭐⭐⭐ 题目描述 ...

  6. python经典面试算法题1.1:如何实现链表的逆序

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.1 如何实现链表的逆序 [腾讯笔试题] 难度系数:⭐⭐⭐ ...

  7. Leetcode OJ 刷题

    Valid Palindrome吐槽一下Leetcode上各种不定义标准的输入输出(只是面试时起码能够问一下输入输出格式...),此篇文章不是详细的题解,是自己刷LeetCode的一个笔记吧,尽管没有 ...

  8. LeetCode第[18]题(Java):4Sum 标签:Array

    题目难度:Medium 题目: Given an array S of n integers, are there elements a, b, c, and d in S such that a + ...

  9. 面试算法爱好者书籍/OJ推荐

    面试算法爱好者书籍/OJ推荐 这个书单也基本适用于准备面试. 一.教科书 基本上一般的算法课本介绍的范围都不会超出算法导论和算法引论的范围.读完这两本书,其它的算法课本大致翻翻也就知道是什么货色了. ...

随机推荐

  1. 萌新学习SpringMVC

    前言 只有光头才能变强. 文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y 这篇SpringMVC被催了很久了,这阵子由于做 ...

  2. React-Redux填坑

    这篇东西以后慢慢补充. Q:今天遇到一个问题是 TypeError:dispatch is not a function A:一直报这个type error,调试了好一阵,最后在tof上看到网友说co ...

  3. 3.6 Go String型

    1. Go String型 Unicode是一种字符集,code point UTF8是unicode的存储实现,转换为字节序列的规则 go的rune类型 可以取出字符串里的unicode 字符串是一 ...

  4. redis集群复制和故障转移

    #### 一.集群的问题- 1.当某个主节点宕机后,对应的槽位没有节点承担,整个集群处于失败状态,不可用,怎么办- 2.如何判断某个主节点是否真正的岩机?- 3.如果从某个主节点的所有从节点中选举出一 ...

  5. 博客营销(Blog Marketing)

    一.什么是博客营销 博客营销(Blog Marketing)的概念可以说并没有严格的定义,简单来说,就是利用博客这种网络应用形式开展网络营销.要说明什么是博客营销,首先要从什么是博客说起. 博客(Bl ...

  6. 在 n 道题目中挑选一些使得所有人对题目的掌握情况不超过一半。

    Snark and Philip are preparing the problemset for the upcoming pre-qualification round for semi-quar ...

  7. xv6 操作系统的环境搭建

    xv6 是 MIT 设计的一个教学型操纵系统.xv6 可在 Intel X86 框架上运行,为了方便,建议将 xv6 运行在 QEMU 虚拟机器上,本人的实验环境是 ubuntu 18.04 . 1. ...

  8. (STL初步)映射:map

    map就是从键(key)到值(value)的映射. 因为重载了[]运算符,map像是数组的”高级版“. 例如,map<string,int>month_name 表示:”月份名字到月份编号 ...

  9. Java——日期获取和日期格式化

    import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...

  10. mysql创建本地用户及赋予数据库权限的方法示例

    大家在安装 mysql 时通常会生成一个超级用户 root,很多人之后就一直沿用这一个用户,虽然这会很方便,但超级用户权限太大,在所有地方使用它通常是一个安全隐患. 这一点跟操作系统的用户管理也是类似 ...