哈希算法题目:无重复字符的最长子串
字数 2423 2025-12-12 08:35:54
哈希算法题目:无重复字符的最长子串
题目描述
给定一个字符串 s,请你找出其中不含有重复字符的 最长子串 的长度。
例如:
- 输入
s = "abcabcbb",最长无重复字符子串是"abc",长度为 3。 - 输入
s = "bbbbb",最长子串是"b",长度为 1。 - 输入
s = "pwwkew",最长子串是"wke",长度为 3(注意"pwke"不是子串,而是子序列,子串必须连续)。
要求:
- 时间复杂度尽可能低,最好在 O(n) 内完成。
- 用哈希表(或数组映射)记录字符出现的最新位置,以快速判断重复字符并更新子串起始点。
解题步骤详解
第一步:问题理解
我们要找一个连续的、内部不包含重复字符的子串,并计算它的最大长度。
关键难点:
- 当遇到重复字符时,不能简单地将当前子串全部舍弃,而是要将子串起点更新到“上一次重复字符的下一个位置”,以确保新子串中没有重复字符。
- 需要一个快速的方法知道重复字符上一次出现的位置,这自然想到用哈希表。
第二步:思路设计
- 用两个指针(索引)
left和right来表示当前无重复字符子串的边界:left指向当前子串的开始位置。right是遍历字符串的索引,指向当前检查的字符。
- 用一个哈希表(比如
unordered_map<char, int>或长度为 128 的数组,适用于 ASCII 字符)来记录每个字符最后一次出现的位置。 - 遍历字符串,对每个
s[right]:- 如果这个字符之前出现过,并且它的上一次出现位置在
left及其右侧(说明在当前子串内重复了),那么将left更新为上一次出现位置的下一个索引,从而“跳过”重复字符,保证新子串不含重复。 - 不管是否重复,更新这个字符在哈希表中的最新位置为
right。 - 计算当前子串长度
right - left + 1,并更新最大长度。
- 如果这个字符之前出现过,并且它的上一次出现位置在
第三步:具体例子推演
以 s = "abcabcbb" 为例:
| right | s[right] | 上一次出现位置 | left 变化 | 当前子串 | 长度 | 最大长度 |
|---|---|---|---|---|---|---|
| 0 | a | -1 | left=0 | a | 1 | 1 |
| 1 | b | -1 | left=0 | ab | 2 | 2 |
| 2 | c | -1 | left=0 | abc | 3 | 3 |
| 3 | a | 0 | 0 ≥ left? 是 → left=0+1=1 | bca | 3 | 3 |
| 4 | b | 1 | 1 ≥ left=1? 是 → left=1+1=2 | cab | 3 | 3 |
| 5 | c | 2 | 2 ≥ left=2? 是 → left=2+1=3 | abc | 3 | 3 |
| 6 | b | 4 | 4 ≥ left=3? 是 → left=4+1=5 | cb | 2 | 3 |
| 7 | b | 6 | 6 ≥ left=5? 是 → left=6+1=7 | b | 1 | 3 |
最终结果为 3。
第四步:算法实现细节
int lengthOfLongestSubstring(string s) {
// 哈希表,key 是字符,value 是该字符最后一次出现的索引
unordered_map<char, int> charIndexMap;
int maxLen = 0;
int left = 0; // 当前无重复子串的起点
for (int right = 0; right < s.size(); ++right) {
char ch = s[right];
// 如果这个字符之前出现过,并且上一次出现的位置在当前子串范围内(即 >= left)
if (charIndexMap.find(ch) != charIndexMap.end() && charIndexMap[ch] >= left) {
// 将 left 移到上一次出现位置的下一个位置
left = charIndexMap[ch] + 1;
}
// 更新字符的最新位置
charIndexMap[ch] = right;
// 计算当前子串长度
int curLen = right - left + 1;
maxLen = max(maxLen, curLen);
}
return maxLen;
}
注意:
- 判断重复时不仅要检查字符是否在哈希表中存在,还要看其上一次出现的位置是否在当前子串内(
charIndexMap[ch] >= left),因为在 left 之前的重复字符已经被“抛弃”了,不影响当前子串。 - 由于字符集有限(比如 ASCII 128),也可以用数组
int lastPos[128]并初始化为 -1,这样更高效。
第五步:复杂度分析
- 时间复杂度 O(n):只需要遍历一次字符串,哈希表的插入和查找是 O(1)。
- 空间复杂度 O(字符集大小):哈希表存储每个字符最后一次出现的位置,最多 128 或 256 个条目(对 ASCII 或扩展 ASCII)。
第六步:思考扩展
- 如果字符集很大(比如 Unicode),哈希表方案依然适用,因为字符种类有限,不会无限增长。
- 类似问题变形:找出最长子串本身(而不仅仅是长度)——可以在更新最大长度时记录起止位置,最后截取子串。
- 如果允许最多重复 k 次(而不是 0 次),可以改为用哈希表记录字符出现次数,结合滑动窗口。
通过以上步骤,你可以理解如何利用哈希表记录字符位置,并配合双指针在 O(n) 时间内高效地找到无重复字符的最长子串长度。