本文始发于个人公众号: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. GraphQL-- 使用Apollo Server搭建Node服务端

    一.关于Apollo Server Apollo Server是一种使用JS创建GraphQL服务端的一个方案.它的兼容性比较好,可以很好地和GraphQL客户端进行兼容.同时它可以 独立作为服务端进 ...

  2. 学习Echarts:(一)静态图表

    Echarts是现在比较火的js图表库,官网有丰富的实例和友好的入门教程.但是图表的种类很多,配置项的参数也很多,一开始我根据图表类型翻看配置项,发现这样学效率太低了,决定先制定一个简单的学习步骤,按 ...

  3. Java Thread中,run方法和start方法的区别

     两种方法的区别: 1.start方法 用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦 ...

  4. MYSQL mysql.user表中权限对应的解释

    命令标识 授权表中对应的列 说明 CREATE Create_priv 创建数据库.表或索引 CREATE TEMPORARY TABLES Create_tmp_table_priv 创建临时数据表 ...

  5. linux 多线程 信号

    一个老系统的问题,用的system v消息队列同步等响应,通过alarm信号来进行超时控制.现在系统进行升级改造(所谓云化),原来进程处理的逻辑全部改成了线程框架,问题就出现了.alarm信号发出的时 ...

  6. [JavaWeb基础] 008.Spring初步配置

    框架简介: Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Develop ...

  7. java方式实现归并排序

    一.基本思想 归并排序是建立在归并操作上的一种排序算法,该算法是采用分治法的一个典型应用.具体操作如下:所谓的分治就是分而治之,以一分为二的原则,先把序列平均分解成二个左右子序列,然后递归左右二个子序 ...

  8. LM NTML NET-NTLM2理解及hash破解

    LM Windows Vista / Server 2008已经默认关闭,在老版本可以遇到,但根据windwos的向下兼容性,可以通过组策略启用它(https://support.microsoft. ...

  9. 舵机MX-64AR与MX-28AR驱动

    背景:硬件采用485通信,在tb上采购的无需收发控制的串口转RS485模块(485通信为半双工,一般情况需要控制收发模式).在使用该模块后,即可完全使用一个普通地串口来对485通信的舵机进行操作. 模 ...

  10. 关于使用npm成功安装命令后,执行时却报找不到命令的问题

    # 使用npm安装newman命令 ~$ npm install newman --global ... /root/node-v6.9.1-linux-x64/bin/newman -> /r ...