leetcode笔记-3.无重复字符的最长子串 - CrowFea

leetcode笔记-3.无重复字符的最长子串 - CrowFea

0.问题描述

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

示例 1:

输入:

1
"abcabcbb"

输出:

1
3

解释: 无重复字符的最长子串是 “abc”,其长度为 3。

示例 2:

输入:

1
"bbbbb"

输出:

1
1

解释: 无重复字符的最长子串是 “b”,其长度为 1。

示例 3:

输入:

1
"pwwkew"

输出:

1
3

解释: 无重复字符的最长子串是 “wke”,其长度为 3。
请注意,答案必须是一个子串,“pwke” 是一个子序列 而不是子串。

1.问题分析

看到这道题的时候想到了是使用哈希表来做,这样就可以在线性时间内完成查重,然鹅这道题还是看了解答emmm。

给了我们一个字符串,让我们求最长的无重复字符的子串,注意这里是子串,不是子序列,所以必须是连续的。从人脑的角度出发,如果给一个例子"abcacbacas",让你手动找无重复字符的子串,该怎么找?一个字符一个字符的遍历,比如a,b,c,然后又出现了一个a,那么此时就应该去掉第一次出现的a,然后继续往后,又出现了一个c,则应该去掉一次出现的c,以此类推,最终发现最长的长度为3。

一开始的思路是记录下所有字母的位置,但是由于字母会重复,这么做的意义其实不大,而且还会混乱。进一步考虑,由于字符会重复出现,到底是保存所有出现的位置呢,还是只记录一个位置?

我们之前手动推导的方法实际上是维护了一个滑动窗口,窗口内的都是没有重复的字符,我们需要尽可能的扩大窗口的大小。由于窗口在不停向右滑动,所以我们只关心每个字符最后出现的位置,并建立映射。窗口的右边界就是当前遍历到的字符的位置,为了求出窗口的大小,我们需要一个变量left来指向滑动窗口的左边界,这样,如果当前遍历到的字符从未出现过,那么直接扩大右边界,如果之前出现过,那么就分两种情况:在或不在滑动窗口内,如果不在滑动窗口内,那么就没事,当前字符可以加进来,如果在的话,就需要先在滑动窗口内去掉这个已经出现过的字符了,去掉的方法并不需要将左边界left一位一位向右遍历查找,由于我们的HashMap已经保存了该重复字符最后出现的位置,所以直接移动left指针就可以了。我们维护一个结果res,每次用出现过的窗口大小来更新结果res,就可以得到最终结果了。

也可以

建立一个256位大小的整型数组来代替哈希表这样做的原因是ASCII表共能表示256个字符,所以可以记录所有字符,然后我们需要定义两个变量res和left,其中res用来记录最长无重复子串的长度,left指向该无重复子串左边的起始位置,然后我们遍历整个字符串,对于每一个遍历到的字符,如果哈希表中该字符串对应的值为0,说明没有遇到过该字符,则此时计算最长无重复子串,i - left +1,其中i是最长无重复子串最右边的位置,left是最左边的位置,还有一种情况也需要计算最长无重复子串,就是当哈希表中的值小于left,这是由于此时出现过重复的字符,left的位置更新了,如果又遇到了新的字符,就要重新计算最长无重复子串。最后每次都要在哈希表中将当前字符对应的值赋值为i+1。

3.代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class  {
public:
int lengthOfLongestSubstring(string s) {
int m[256]={0}, i,left=0,res=0;
for (i = 0; i < s.size(); i++) {
if (m[s[i]] == 0||m[s[i]]<left) {
if (res < i - left + 1) {
res = i - left + 1;
}
}
else {
left = m[s[i]];
}
m[s[i]] = i + 1;
}
return res;
}
};

这里解释下程序中那个if条件语句中为啥要有个m[s[i]] < left,我们用一个例子来说明,当输入字符串为"abbca"的时候,当i=4时,也就是即将要开始遍历最后一个字母a时,此时哈希表中a对应1,b对应3,c对应4,left为2,即当前最长的子字符串的左边界为第二个b的位置,而第一个a已经不在当前最长的字符串的范围内了,那么对于i=4这个新进来的a,应该要加入结果中,而此时未被更新的哈希表中a为1,不是0,如果不判断它和left的关系的话,就无法更新结果,那么答案就会少一位,所以需要加m[s[i]] < left。

4.精简代码

1
2
3
4
5
6
7
8
9
10
11
12
13
class  {
public:
int lengthOfLongestSubstring(string s) {
vector<int> m(256, -1);
int res = 0, left = -1;
for (int i = 0; i < s.size(); ++i) {
left = max(left, m[s[i]]);
m[s[i]] = i;
res = max(res, i - left);
}
return res;
}
};

那么最后用心心念念的哈希表来做一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class  {
public:
int lengthOfLongestSubstring(string s) {
int res = 0, left = 0, i = 0, n = s.size();
unordered_map<char, int> m;
for (int i = 0; i < n; ++i) {
left = max(left, m[s[i]]);
m[s[i]] = i + 1;
res = max(res, i - left + 1);
}
return res;
}
};

5.Java解法

Java解法一样采用c++的思路求解,个人感觉java的方法确实是真的麻烦…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class  {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int maxLen = 0;
HashMap<Character, Integer> map = new HashMap<>();
for(int i = 0, j = 0; j < n; j++){
if(map.containsKey(s.charAt(j)))
i = Math.max(i, map.get(s.charAt(j)));
maxLen = Math.max(maxLen, j-i+1);
map.put(s.charAt(j),j+1);
}
return maxLen;
}
}

6.Python解法

1
2
3
4
5
6
7
8
9
10
class :
def lengthOfLongestSubstring(self, s: str) -> int:
st={}
i,ans=0,0
for j in range(len(s)):
if s[j] in st:
i=max(i,st[s[j]])
ans=max(ans,j-i+1)
st[s[j]]=j+1
return ans




原文:大专栏  leetcode笔记-3.无重复字符的最长子串 - CrowFea