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