重复次数最多的 子串_手撕面试官:双指针无重复字符的最长子串

本文通过一个面试场景介绍了如何使用双指针法解决寻找字符串中不包含重复字符的最长子串长度的问题。通过优化算法,将时间复杂度降低到O(n),空间复杂度为O(1)。具体实现中,利用一个大小为128的数组记录字符出现的次数,当出现重复字符时,移动左指针i,确保子串不包含重复元素。这种方法提高了算法效率。
摘要由CSDN通过智能技术生成

    正在找工作的猿六凯在投出无数份简历后,准备看会儿电影放松下。突然接到面试官的电话,急忙跑到厕所接听。

39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

小猿同学,看你简历说算法掌握的比较扎实,给你出道算法题。

39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。

39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

比如:输入: "abcabcbb",输出: 3。因为无重复字符的最长子串是 "abc",所以其长度为 3。

猿六凯

找出字符串中的所有子串,然后对每一个进行判读是否包含重复字符,把不包含重复字符的最大子串长度输出来就好。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

写下代码吧。

class Solution {public:    int lengthOfLongestSubstring(string s) {          int res = 0;//保存结果        for(int i = 0; i < s.size(); i++){            for(int j = i; j >= 0; j--){//双重遍历,获得以s[i]为结尾的每一个子串                 set<int> myset;                for(int k = j; k <= i; k++){                       myset.insert(s[k]);//把字符去重                }                if (myset.size() == i - j + 1)//如果不同字符个数与子串长度相同,则子串不包含重复字符。                    res = max(res, i - j + 1);                else                    break;//如果出现了重复字符,j就不用再扩展了。            }        }              return res;    }};
39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

时间复杂度和空间复杂度是多少?

猿六凯

用两个循环找出所有子串:第一个循环共n次;第二个循环开始时是0次,最后是n次,总共0 + 1 + 2 + ...+ n = n*(0+n)/2。再用一个set判断是否有重复元素,这个过程的时间复杂度是O(n)。所以总时间复杂度是是O(n^3)。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

开辟了一个与字符串长度线性相关的set,空间复杂度是O(n)。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

有没有更好的方法?

猿六凯

我想想,可用双指针。开始时,用两个指针i,j指字符串第一个元素。向右一步步向右扩展j指针,过程中,如果i,j之间的子串出现了重复元素,就向左移动i指针,直到i,j之间没有重复元素。这样,指针i,j之间的字符串含义就是:以j指向元素为结尾的最长无重复元素子串。

例如处理abcab过程如下:

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

用res保存最长的不包含重复字符的子串长度,开始时res = 0。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 0f9edf3cd1b9e6eeb9a4be1e9c786b19.png

猿六凯

用两个指针i,j分别指向字符串的第一个字符,这个时候i,j之间的字符串只有一个字符a,肯定不包含重复元素。故以j指向的元素a为结尾的最长不包含重复元素子串为:a,长度为j - i + 1 = 1。1 > res,就更新res = j - i +1。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 8b17e7a8423236619b8602fcd8b57723.png

猿六凯

j指针向后移动一格,i,j之间的字符串中字符串为ab,不包含重复字符。故以j指向的元素b为结尾的最长不包含重复字符的子串为:ab,长度为:j - i + 1 = 2 > res,更新res:res = j - i + 1 = 2。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 2d492f3015ec1760017cad34b4f65917.png

猿六凯

j指针继续向后移动一格,i,j之间的字符串中字符串为abc,不包含重复字符。故以j指向的元素c为结尾的最长不包含重复字符的子串为:abc,长度为:j - i + 1 = 3 > res,更新res:res = j - i + 1 = 3。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 06a0d58f5df67d685876c5a0b6547ae3.png

猿六凯

j指针继续向后移动一格,i,j之间的字符串中字符串为abca,包含重复字符。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 86e5262371b577d469837e8bb6747feb.png

猿六凯

这时候需要将i指针向左移动,使得i,j之间的字符串中没有重复字符。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

i向左移动一格后,i,j之间的字符串为bca。没有重复字符。故以j指向的元素a为结尾的最长不包含重复字符的子串为:bca,长度为:j - i + 1 = 3 = res。res 保持不变。如下图

9afe4e391f8ff433dc4fe0953b7655bd.png 0b8b8041f8c35f77ad3f556a9f11cbaa.png

猿六凯

j继续往后移动一格,此时i,j之间的字符串为bcab,包含重复字符。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 537b087f74d9cd6be52bbead42d439d5.png

猿六凯

需要将i指针向左移,直到i,j之间的字符串中没有重复字符。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

i移动一格后,i,j之间的字符串为cab,没有重复字符。故以j指向的元素b为结尾的最长不包含重复字符的子串为:cab,长度为:j - i + 1 = 3 = res。res 保持不变。如下图。

9afe4e391f8ff433dc4fe0953b7655bd.png 20d09fe7e03daa276c65685ae38dfdd2.png

猿六凯

j指向了字符串结尾,结束。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

整个过程中,通过j对字符串的遍历,求出了以各个元素为结尾的最长不包含重复字符的子串长度。并且把最大的长度保存在了res中。res就是答案。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

过程中需要判断j指向的元素是重复,如果重复,i向左移。这个过程具体怎么实现?

猿六凯

可以记录下一下i,j之间的字符串中各个字符出现的次数。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

开始时,i,j之间没有字符。所以各个字符串出现次数为0。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 086cc5480cae0dbd60a6502f0bf75335.png

猿六凯

i,j指向第一个元素a时,a字符出现了一次,a出现的次数+1,更新为a:1。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png b9b3f038d5daac9a4f5253dd2fef7d84.png

猿六凯

j往后移动一格后,j指向b,i,j指向之间的字符串中b出现的次数增加1,更新为b:1。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 4b527d36456b6c48548f4b72a3b0a575.png

猿六凯

j再次往后移动一格后,j指向c,i,j指向之间的字符串中,c出现的次数增加1,更新为c:1。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 9e6dcda91d69acd0acd8f6f8b6986908.png

猿六凯

j再次往后移动一格后,j指向a,i,j指向之间的字符串中,a出现的次数增加1,更新为a:2。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png c8ee8d3f6ef16173948d7923f4ddbbd8.png

猿六凯

这个时候,i,j之间的字符串中,a出现了2次。所以需要向左移动i,减少一次a出现的次数。

9afe4e391f8ff433dc4fe0953b7655bd.png

猿六凯

i向左移动一格,i由指向a变成指向b,i,j之间字符串中a出现的次数减少1,更新为a:1。这个时候i,j之间各个字符出现的次数都为1。所以i,j之间的字符串就是以j指向的的元素b为结尾的不包含重复字符的子串。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 44dc1a31a96c27c6512af6abcf221f84.png

猿六凯

概括一下就是:维护一个数组,来记录i,j之间各个字符出现的次数。j向右移动之前,各个字符出现的次数都小于2。j向右移动后,j指向的字符出现次数+1,如果现的次数变为2,就向左移动i。i每移动一格,i指向的元素出现次数-1。直到i走过与j更新后指向的那个元素重复的字符后,该元素出现次数有2变为1。i,j之间的字符串就不包含重复元素了。如下图:

9afe4e391f8ff433dc4fe0953b7655bd.png 8576200fa38c1f117cd0cfa84cc88bb9.png fd03e12d2977020a87ea892e86a78d40.png

猿六凯

i从指向a往右移动一格后,i,j之间的字符串中,a出现的次数减少1,变为1。这个时候,i,j之间就没有重复字符了。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

具体的,用什么记录字符出现的次数?

猿六凯

计算机中,字符有127个,它们的ascii码值时从1-127。我们开辟一个大小为128的整数数组,把各个元素初始化为0。遇到一个字符,就把它ascii码对应的值+1。就能在O(1)的时间内进行判断了。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

说的不错,写下代码吧

class Solution {public:    int lengthOfLongestSubstring(string s) {        int heap[128] = {0};//记录数组,初始化为0        int res = 0;        for(int i = 0, j = 0; j < s.size(); ++j){//依次添加新的字符            heap[s[j]]++;//添加字符出现的次数+1            while(heap[s[j]] > 1){//如果添加字符出现次数大于1                heap[s[i]]--;//i指向的的符出现次数-1                i++;//i向后移动            }//直到新添加的字符出现次数变为1            res = max(res, j - i + 1);//取新字符串长度与上一个字符串长度最大值        }        return res;    }};
39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

分析下时间复杂度和空间复杂度。

猿六凯

在时间上,指针i遍历了一遍数组,指针j在i的遍历过程中,也只遍历了一遍数组,所以时间复杂度是O(n)。空间上开辟了一个固定大小的数组,所以空间复杂度是O(1)。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

恩,今天先到这,等下一轮面试吧。

猿六凯

恩恩,谢谢黑脸面试官。

9afe4e391f8ff433dc4fe0953b7655bd.png 39bd7fe96464f724e58b601e3c0d2ae1.png

黑脸面试官

你说谁脸黑,你没下一次面试了。

对应leetcode题目:3. 无重复字符的最长子串,难度:中等。

此思路解题详情:

d244184440b2f36ad5be8c1a5e6fdff8.png

    欢迎订阅,每周更新面试高频算法题。从思路,代码,时间复杂度等多方面进行分析。

54d56d68276338cdb3f287eb43f42ea4.png

关注后点击联系我,加我好友备注加群,拉你进刷题打卡群,每月抽奖送奶茶。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值