LeetCode 76,一题教会你面试算法时的思考套路
本文始发于个人公众号: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,一题教会你面试算法时的思考套路的更多相关文章
- LeetCode 第70题动态规划算法
导言 看了 动态规划(https://www.cnblogs.com/fivestudy/p/11855853.html)的帖子,觉得写的很好,记录下来. 动态规划问题一直是算法面试当中的重点和难点, ...
- python经典面试算法题1.4:如何对链表进行重新排序
本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.4 对链表按照如下要求重新排序 [微软笔试题] 难度系数: ...
- python经典面试算法题1.3:如何计算两个单链表所代表的数之和
本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.2 如何实现链表的逆序 [华为笔试题] 难度系数:⭐⭐⭐ ...
- python经典面试算法题1.2:如何从无序链表中移除重复项
本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.2 如何实现链表的逆序 [蚂蚁金服面试题] 难度系数:⭐⭐ ...
- python经典面试算法题4.1:如何找出数组中唯一的重复元素
本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. [百度面试题] 难度系数:⭐⭐⭐ 考察频率:⭐⭐⭐⭐ 题目描述 ...
- python经典面试算法题1.1:如何实现链表的逆序
本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.1 如何实现链表的逆序 [腾讯笔试题] 难度系数:⭐⭐⭐ ...
- Leetcode OJ 刷题
Valid Palindrome吐槽一下Leetcode上各种不定义标准的输入输出(只是面试时起码能够问一下输入输出格式...),此篇文章不是详细的题解,是自己刷LeetCode的一个笔记吧,尽管没有 ...
- 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 + ...
- 面试算法爱好者书籍/OJ推荐
面试算法爱好者书籍/OJ推荐 这个书单也基本适用于准备面试. 一.教科书 基本上一般的算法课本介绍的范围都不会超出算法导论和算法引论的范围.读完这两本书,其它的算法课本大致翻翻也就知道是什么货色了. ...
随机推荐
- 读懂操作系统(x64)之堆栈帧(过程调用)
前言 上一节内容我们对在32位操作系统下堆栈帧进行了详细的分析,本节我们继续来看看在64位操作系统下对于过程调用在处理机制上是否会有所不同呢? 堆栈帧 我们给出如下示例代码方便对照汇编代码看,和上一节 ...
- MyBatis缓存机制(一级缓存,二级缓存)
一,MyBatis一级缓存(本地缓存) My Batis 一级缓存存在于 SqlSession 的生命周期中,是SqlSession级别的缓存.在操作数据库时需要构造SqlSession对象,在对象中 ...
- 5.2 Go 包与函数
5.2 Go 包与函数 在多个包中相互调用函数,需要用到Go包的知识. 代码组织如下: 思路: 1.定义功能函数calc放入到utils.go,将utils.go放在utils文件夹/包中,当其他文件 ...
- Django操作session
session是存放在服务端的,在django中使用session必须要先在数据库中创建django_session表,session相关信息都要依赖此表 获取session request.sess ...
- Django之MTV模式
MTV与MVC+url控制器 MVC框架: · M:model.py 就是和数据库打交道用的,创建表等操作 · V:View 视图(视图函数:逻辑处理响应函数,ht ...
- mac OS和win7笔记本实现文件共享
记录下macbook通过共享读取win7笔记本中文件的过程,条件是两台电脑处于同一无线网中 win7操作 点击网络图标--属性--更改高级共享设置 选择公用后选择以下选项:启动网络发现--启动文件和打 ...
- EventBus/EventQueue 再思考
EventBus/EventQueue 再思考 Intro 之前写过两篇文章,造轮子系列的 EventBus/EventQueue,回想起来觉得当前的想法有点问题,当时对 EvenStore 可能有点 ...
- 【Java】面试官灵魂拷问:if语句执行完else语句真的不会再执行吗?
写在前面 最近跳槽找工作的朋友确实不少,遇到的面试题也是千奇百怪,这不,一名读者朋友面试时,被面试官问到了一个直击灵魂的问题:if 语句执行完else语句真的不会再执行吗?这个奇葩的问题把这名读者问倒 ...
- element 的 Cascader 级联选择器设定默认值
Cascader 级联选择器 发现在很多的CRM管理系统里面,都有不少页面是用到这种级联选择器的,确实,功能很实用, 不过要设置默认值则应该让不少人头痛,因为你选择的时候 @change 事件的参数就 ...
- Verilog代码和FPGA硬件的映射关系(三)
组合逻辑和FPGA之间的映射关系我们知道了,那时序逻辑和FPGA之间又是一种怎样的映射关系呢?我们就以前面寄存器章节的例子来向大家说明,也一同把当时为什么用异步复位更节约资源的原因告诉大家.我们先来看 ...