一、题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc"
,所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b"
,所以其长度为 1。
示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是"wke"
,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke"
是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 10^4
s
由英文字母、数字、符号和空格组成
二、解题思路
- 初始化两个指针,
left
和right
,分别表示当前考虑的子串的起始和结束位置。 - 创建一个哈希表(HashMap),用于存储字符及其在字符串中的索引。
- 当
right
指针向右移动时,更新哈希表中字符的索引。 - 如果哈希表中已经存在当前字符,且该字符的索引大于等于
left
指针的位置,说明子串中有重复字符,需要移动left
指针到重复字符的下一个位置。 - 更新最长子串的长度,即
right - left + 1
。 - 当
right
指针到达字符串末尾时,返回最长子串的长度。
三、具体代码
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int maxLength = 0;
Map<Character, Integer> map = new HashMap<>();
int left = 0;
for (int right = 0; right < s.length(); right++) {
char currentChar = s.charAt(right);
// 如果当前字符已经存在于哈希表中,并且索引大于等于left
if (map.containsKey(currentChar) && map.get(currentChar) >= left) {
// 更新left指针位置
left = map.get(currentChar) + 1;
}
// 更新当前字符的索引
map.put(currentChar, right);
// 更新最长子串长度
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
}
四、时间复杂度与空间复杂度
1. 时间复杂度
- 时间复杂度是 O(n),其中 n 是字符串
s
的长度。 - 这是因为
right
指针遍历整个字符串,而left
指针在遇到重复字符时最多向后移动到重复字符的下一个位置。 - 在最坏的情况下,
left
指针可能需要移动到字符串的开始位置,但这种情况只会发生一次,所以整体时间复杂度仍然是线性的。
2. 空间复杂度
- 空间复杂度是 O(min(n, m)),其中 m 是字符集的大小(假设字符集是有限的,例如 ASCII 字符集有 128 个字符)。
- 这是因为哈希表
map
用于存储字符到索引的映射,最多需要存储 n 个字符的索引。 - 在实际应用中,如果字符集的大小远小于字符串长度,空间复杂度将主要由字符集的大小决定。
五、总结知识点
-
滑动窗口:这是一种常用的解决子串问题的方法,通过移动窗口的边界来遍历字符串,同时保持窗口内元素的特性(如无重复字符)。
-
哈希表(HashMap):用于存储字符及其在字符串中的索引,以便快速查找和更新。
-
双指针:使用
left
和right
指针来表示滑动窗口的起始和结束位置,通过移动这两个指针来控制窗口的大小。 -
边界条件处理:在代码中,我们处理了字符串为空或为
null
的情况,以及在遇到重复字符时更新left
指针的逻辑。 -
数学优化:通过计算
right - left + 1
来动态更新最长子串的长度,这是一种常见的优化技巧,避免了额外的变量来存储当前子串的长度。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。