LeetCode 第3题:无重复字符的最长子串
题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
难度
中等
题目链接
https://leetcode.cn/problems/longest-substring-without-repeating-characters/
示例
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成
解题思路
方法一:滑动窗口
这道题可以使用滑动窗口的方法解决。滑动窗口是一种在数组或字符串上使用双指针的技巧,通过移动左右指针形成一个窗口,在窗口内进行操作。
关键点:
- 使用两个指针(left和right)维护一个滑动窗口
- 使用HashSet记录窗口内的字符,保证无重复
- 当遇到重复字符时,移动左指针直到删除重复字符
- 在移动过程中更新最大长度
具体步骤:
- 初始化左指针left = 0,右指针right = 0
- 创建HashSet存储窗口内的字符
- 移动右指针,将字符加入Set:
- 如果字符不在Set中,加入Set并更新最大长度
- 如果字符在Set中,移动左指针并删除字符,直到没有重复
- 重复步骤3直到右指针到达字符串末尾
时间复杂度:O(n),其中n是字符串长度
空间复杂度:O(min(m,n)),其中m是字符集大小
方法二:优化的滑动窗口
使用Dictionary/HashMap代替HashSet,存储字符最后出现的位置,可以直接跳转左指针位置,避免一个个删除字符。
代码实现
C# 实现(基本滑动窗口)
csharp
public class Solution {
public int LengthOfLongestSubstring(string s) {
// 特殊情况处理
if (string.IsNullOrEmpty(s)) return 0;
// 创建HashSet存储当前窗口中的字符
HashSet<char> window = new HashSet<char>();
int maxLength = 0;
int left = 0;
// 右指针遍历字符串
for (int right = 0; right < s.Length; right++) {
// 如果发现重复字符,移动左指针直到删除重复字符
while (window.Contains(s[right])) {
window.Remove(s[left]);
left++;
}
// 将当前字符加入窗口
window.Add(s[right]);
// 更新最大长度
maxLength = Math.Max(maxLength, right - left + 1);
}
return maxLength;
}
}
C# 实现(优化的滑动窗口)
csharp
public class Solution {
public int LengthOfLongestSubstring(string s) {
// 特殊情况处理
if (string.IsNullOrEmpty(s)) return 0;
// 创建Dictionary存储字符最后出现的位置
Dictionary<char, int> lastPos = new Dictionary<char, int>();
int maxLength = 0;
int left = 0;
// 右指针遍历字符串
for (int right = 0; right < s.Length; right++) {
char currentChar = s[right];
// 如果字符已存在,直接将左指针移动到重复字符的下一位
if (lastPos.ContainsKey(currentChar)) {
left = Math.Max(left, lastPos[currentChar] + 1);
}
// 更新字符最后出现的位置
lastPos[currentChar] = right;
// 更新最大长度
maxLength = Math.Max(maxLength, right - left + 1);
}
return maxLength;
}
}
代码详解
基本滑动窗口版本:
HashSet<char> window
:用于存储当前窗口中的字符,保证无重复while (window.Contains(s[right]))
:当发现重复字符时,不断移动左指针并删除字符maxLength = Math.Max(maxLength, right - left + 1)
:更新最大长度
优化版本:
Dictionary<char, int> lastPos
:存储每个字符最后出现的位置left = Math.Max(left, lastPos[currentChar] + 1)
:- 当发现重复字符时,直接将左指针移动到重复字符上次出现位置的下一位
- 使用Math.Max是为了防止左指针回退
执行结果
基本滑动窗口版本:
- 执行用时:84 ms
- 内存消耗:39.5 MB
优化版本:
- 执行用时:72 ms
- 内存消耗:39.8 MB
总结与反思
- 这是一道经典的滑动窗口题目,考察了:
- 滑动窗口的基本应用
- 字符串的处理
- 哈希表的使用
- 两种实现方式各有优势:
- 基本版本思路更直观,易于理解
- 优化版本性能更好,避免了多次移动左指针
- 关键点:
- 理解子串和子序列的区别
- 正确维护滑动窗口
- 高效处理重复字符
相关题目
- LeetCode 第159题:至多包含两个不同字符的最长子串
- LeetCode 第340题:至多包含 K 个不同字符的最长子串
- LeetCode 第395题:至少有 K 个重复字符的最长子串
- LeetCode 第424题:替换后的最长重复字符