Redian新闻
>
LeetCode刷题实战3:最长不重复子串

LeetCode刷题实战3:最长不重复子串

科技



算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !


今天和大家聊的问题叫做最长不重复子串,这道题很有意思,我们先来看题面:


Given a string, find the length of the longest substring without repeating characters.

https://leetcode.com/problems/longest-substring-without-repeating-characters/


翻译


题目只有一句话:给定一个字符串,要求返回不包含重复字符的最长子串的长度。


样例


Example 1:
Input: "abcabcbb"Output: 3 Explanation: The answer is "abc", with the length of 3. Example 2:
Input: "bbbbb"Output: 1Explanation: The answer is "b", with the length of 1.Example 3:
Input: "pwwkew"Output: 3Explanation: The answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.


分析


我们先从最简单的方法开始,最容易想到的算法就是暴力枚举。我们可以遍历出这个字符串当中所有的子串,之后再判断这个子串当中有没有出现重复的元素。如果没有重复的元素,那么我们就更新答案。


在开始编码之前,我们先仔细观察样例:


我们可以计算出这种算法的复杂度,假设字符串长度是n,那么我们它的所有子串应该有种。

再乘上我们遍历这个子串时候的长度,所以最后的结果是:




显然这样的复杂度是无法接受的,下面我们进行第一个优化。

思考一个问题,在不能有重复字符的限制下,我们真的有必要枚举所有的子串吗?

其实是没有的,在这个规则的限制下,对于字符串当中的每一个起始位置,我们能找到的最长的合法子串必然是确定而且是唯一的。换句话说,对于一个确切的开头而言,我们只需要顺着它一直往后遍历,如果遇到的字符没有出现过就继续,如果已经出现过了,那么当下的字符串就是这个开头对应的最佳答案。

我们用样例举个例子:

假设S=abcabcbb

我们从S[0]开始,我们遍历b,再遍历c,接着遍历a。a已经出现过了,所以abc就是以S[0]开头的最佳答案。对于S当中的每一个位置,我们都可以找到它对应的局部最佳答案。之后,我们只需要在这当中找出最大的长度即可。

我们用Python写出代码:


ret = 0
for i in range(n):
char_set = set()
mid_ret = 0
for j in range(i + 1, n):
if S[j] in char_set:
mid_ret = j - i
break
else
:
char_set.add(S[j])
ret = max(mid_ret, ret)
return ret


这种方法的复杂度就很好算了,对于S而言,它一共有n个位置可以作为起始,每个起始位置,最多遍历n次,所以整体的复杂度应该是


到这里,基本上是我们通过平常思维能够想到的极致了,但是它远不是算法的极值。这道题还存在很大的优化空间,在我们继续探索之前,我们需要先来学习一个新的算法。它的名字叫做“尺取法”,在一些课本当中也被称为two pointer算法或者是两指针算法,也叫滑动窗口算法。


算法的本意是,在一些对区间有所限制的问题当中,我们可以通过维护合法区间的左右边界。通过区间移动找出所有合法的区间,最后找到最终的答案


在本题当中,我们可以把一个不包含重复字符的子串当做是原字符串的一个合法区间。比如在字符串 “abcabcbb” 当中[0, 2]就是一个合法区间。我们用两个记录下标的指针l和r来记录这个区间的左右端点,注意这里的区间我们用的是闭区间。也就是说 l=0,r=2,区间表示好了,怎么移动区间呢?


[a b c] a b c b b


很简单,我们每次往右移动一位,也就是r += 1,区间变成:


[a b c a] b c b b


r往右移动一位,就会读入新的字符,那样整个区间的合法性可能就破坏了。比如我们r加1了之后,读入了a,字符串中多了一个a,那就不是合法区间了。


没关系,我们还有区间的左边界,我们可以再移动区间的左边界,退出一些字符,直到区间重新变成合法区间为止,在这个例子当中,l只需要移动一位就可以满足条件:


a [b c a] b c b b


也就是说新的区间变成了[1, 3],这样就完成了区间的移动。如果r移动了之后,依旧没有出现重复字符呢?没关系,我们继续往下移动就可以了。在这题当中,[0, 0]一定是一个合法的区间,我们可以从[0, 0]开始,通过移动的方式遍历出所有的合法区间。这些合法区间当中,一定有一个是最终的答案,那么我们的问题也就解决了。


我们再来看一下这种算法的复杂度,它的复杂度是。有人会说,我们用了两个指针,不应该也是的复杂度吗?其实不然,看复杂度不能简单只看用了几个循环变量,而需要分析算法当中究竟执行了多少计算量。怎么证明算法复杂度呢?我们怎么知道窗口到底移动了多少次呢?


不知道移动了多少次也可以,方法很简单,我们分析最坏的情况。算法的起始状态是l=0, r=0。当r=n时算法结束,我们不知道此时l等于多少,不过没关系。在算法运行的当中,l和r都是递增的,每次窗口移动最多增加1,那么最多应该执行了2n次(l和r各移动n次)。如此一来,显然这是一个的算法。


算法讲完了,还有一个细节没讲清楚,我们怎么维护区间合法呢?


也很简单,我们维护一个map,记录区间内的字符出现了多少次。我们遇到新的字符,就在map中加一,退出字符,就在map中减一。


Talk is cheap, show me the code。我们写出code来看看:


l, r = 0, 0
ret = 0
char_dict = {}
char_dict[S[0]] = 1
for r in range(1, n):
char_dict[S[r]] += 1
# 当S[r]位置的字符大于1,说明区间非法,开始移动区间左侧
# 最多l=r时结束,不用担心越界
while char_dict[S[r]] > 1:
char_dict[S[l]] -= 1
l += 1
ret = max(ret, r - l + 1)
return ret

代码看起来依然很简洁,编码复杂度几乎没有增加。到这里,我们虽然已经将算法优化成了,但是并没有结束,这题还有优化的空间

那么,怎么做进一步优化呢?

敏感的同学在观察上面这段代码的时候,可能会觉得中间的while循环有点别扭。虽然我们经过分析l最多也就移动到r的位置,不会出现越界等问题,但看到循环里套了循环还是会觉得不太舒服。

我们的下一个优化,就和这个循环有关。那么怎么才能把循环去掉呢?我们先从它产生的原因入手,我们之所以需要一个循环,是因为我们并不知道引起重复的S[r]这个字符在区间里出现的位置在什么地方,如果我们能够知道,那么就很简单,我们直接把l移动到它的右边即可

我们能不能知道呢?当然是可以的,需要我们对dict做一点点改动。我们dict不能再存储字符出现的次数,我们需要存储它最后一次出现的位置。这样,我们对上面的代码稍作修改就可以满足要求了:


l, r = 0, 0
ret = 0
char_dict = {}
char_dict[S[0]] = 0
for r in range(1, n):
# char_dict[S[r]] >= l这个判断是精髓
if S[r] in char_dict and char_dict[S[r]] >= l:
l = char_dict[S[r]] + 1
char_dict[S[r]] = r
ret = max(ret, r - l + 1)
return ret



到这里,这道题就算是讲解完了。尺取法这个算法虽然不难,但是仔细琢磨挺有意思。如果有没有理解的同学,可以结合代码以及样例仔细思考一下,算法不难,我想大家都能学会,衷心希望大家都能有所收获。


如果喜欢本文,请顺手点个赞或者转发吧。

上期推文:

LeetCode刷题实战1:在数组上遍历出花样

LeetCode刷题实战2:用链表模拟加法


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
大报文问题实战原创《陈寅恪学问之“不古不今”》 其一Floods Set 75 Crocodiles Loose in Guangdong起名记---写给宝宝的信(2)很多华人去Costco都这么干!然而,Costco出手了…CodeGeeX2-6B开源,最低6GB显存,性能优于 StarCoder开源打败闭源?Meta即将推出开源代码生成平台Code Llama,剑指OpenAI Codex发现了比LeetCode好用10倍的刷题网站,我2个月转码成功!首发!2024 QS世界大学排名揭晓!英国高校集体霸榜,LSE刷新历史!什么?Costco会员大涨价?这年头Costco都开始涨价了?开票啦!连续三年班夫山地电影节精选,六场不重复,两个周末,一口气看完!《滿山紅葉似彩霞》Costco有机草莓恐携有甲肝病毒?爱逛Costco必看!可惜没有像爱大自然那样更早爱上你Costco7月零食大赏!照着买,再也不用羡慕别人家的costco了!开源打败闭源?Meta 即将推出开源代码生成平台 Code Llama,剑指 OpenAI Codex2023文献情报中心—IEEE讲堂——IEEE学术论文投稿与开放获取出版澳业主持有房产最长区揭晓!悉墨业主最长情,平均12年量化直播|经典量化圣经一次搞定!业界面试题实时更新,高效刷题!【征稿速递】第七届IEEE能源互联网与能源系统集成会议(IEEE EI² 2023)诚邀投稿!【视频】这是有多爱 Costco?亚裔妹子举办 Costco 主题生日派对!就连客人也要扮成……【邀请函】IEEE中国作者研讨会——来自IEEE编委的发文建议及最佳实践商科留学生转码必备:Leetcode到底该怎么刷?(附高效刷题指南)仅剩3席|经典量化圣经 一次搞定!业界面试题实时更新,高效刷题!LeetCode刷题实战1:在数组上遍历出花样去Costco要带ID!Costco开始严查借会员卡,自助结账也要确认本人FB大神的《算法通关宝典》,分分钟日穿LeetCode想知道史上最最最好评的Costco食品吗?快来一起看看来自Costco粉丝的选择![9月26日]科学历史上的今天——金·赫尔尼(Jean Amédée Hoerni)发现了比LeetCode好用的刷题网站,我一口气拿下了Tiktok的Offer只给大模型LeetCode编号,也能解题!大模型表现好是源于对训练数据的记忆吗?请不要迷信大模型赚翻!女子将Costco商品3倍价格卖出!本周Costco优惠信息七国峰会,严岛神社和弥山别再试!女子Costco购物「被终生禁买」礼品卡也将「无效」Costco严打这行为!免费试听|系统梳理数理、金融、编程Technical知识点,真题实训 + 面试技巧全覆盖,让你刷题快人一步!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。