0%

力扣每日一题2021/8/31

题目:3. 无重复字符的最长子串

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

难度:中等

示例 1:

输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
  请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。

示例 4:

输入: s = “”
输出: 0

提示:

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、数字、符号和空格组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  1. HashSet
  2. HashMap
  3. 滑动窗口

解题代码

HashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int lengthOfLongestSubstring(String s) {
int length = s.length(), maxLength = 0;
HashSet hashSet = new HashSet();
for (int i = 0; i < length; i++){
if (!hashSet.add(s.charAt(i))){
maxLength = Math.max(maxLength, hashSet.size());
i -= hashSet.size();
hashSet = new HashSet();
}
}
return Math.max(maxLength, hashSet.size());
}
}

HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int lengthOfLongestSubstring(String s) {
int length = s.length(), maxLength = 0;
HashMap<Character, Integer> hashMap = new HashMap<>();
for (int i = 0; i < length; i++){
char c = s.charAt(i);
if (!hashMap.containsKey(c)){
hashMap.put(c, i);
}else if (hashMap.get(c) != i){
i = hashMap.get(c);
maxLength = Math.max(maxLength, hashMap.size());
hashMap = new HashMap<>();
}
}
return Math.max(maxLength, hashMap.size());
}
}

优化的HashMap(评论区大佬题解)

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

滑动窗口(快慢指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int lengthOfLongestSubstring(String s) {
int length = s.length(), maxLength = 0;
HashSet hashSet = new HashSet();
int fast = 0, slow = 0;
while (fast < length){
if (hashSet.add(s.charAt(fast))){
fast++;
}else {
hashSet.remove(s.charAt(slow++));
}
maxLength = Math.max(maxLength, fast - slow);
}
return maxLength;
}
}

官方解题代码

滑动窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.remove(s.charAt(i - 1));
}
while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
// 不断地移动右指针
occ.add(s.charAt(rk + 1));
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = Math.max(ans, rk - i + 1);
}
return ans;
}
}

总结

  1. 该题最容易想到的是HashSet,这也是我最先想到的题解,但是在遇到重复元素时我的处理是将指针回溯到HashSet第一次添加元素的位置,并将HashSet清空(即重新创建一个空的HashSet),但进行了许多不必要的回溯,而且不断创建新的HashSet也造成了空间浪费
  2. 第二种思路我采用了HashMap来保存元素并将其索引作为值,如('s', 2),当HashMap中已存在字符s就将i回溯到s的下一个字符,将该字符作为新子串的起始字符,接着循环,此思路解决了i的不必要回溯问题,但HashMap中的元素是无重复子串的元素,此时却出现了重复元素,而且我并不知道该重复元素位于子串的哪个位置,无法有效的将该重复元素之前的无效子串进行删除,所以我简单粗暴的将HashMap清空,操作起来就是再重新创建一个空的HashMap,空间的浪费并没有解决,而且此方法的效率并没有与HashSet相差多少,可能原因:abcdae,该字符串匹配到第二个ai回溯到b,与思路1的HashSet一样,所以效率没有了优势
  3. 优化的HashMap,是一个评论区大佬的代码,解决i回溯问题的思路与思路2的HashMap一致,但对出现重复元素的操作十分巧妙,声明一个start指针指向有效起始字符位置,当HashMap中已存在字符s就将start指向s的下一个字符(指向时需要判断HashMap.get(‘s’)是否大于start,即判断s的上一次位置是否在有效无重复子串中,如果不在则不必回溯否则会出错),并通过put('s', i)将新的i赋给s,以上可通过手动模拟字符串abccba理解,跟着代码走一遍,一切都可理解
  4. 滑动窗口是根据官方解题思路写的代码,写完后又看了官方代码,感觉与自己写代码习惯有些不同,有点不好理解,不过跟着代码走一遍时感觉这才是还原官方思路的代码