题目来源
题目描述
给定一个字符串 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 由英文字母、数字、符号和空格组成
题目解析
本题很容易想到需要使用滑动窗口解题。
即定义两个指针 L,R,初始时 L,R 都初始化为0,表示 s 串的 [L, R)区间为一个滑动窗口。我们需要保证滑窗内部不能含有重复字符。
那么如何高效地判断滑窗内是否含有重复字符呢?
这里我们可以定义一个128长度的数组index,或者一个哈希表来记录 R 指针扫描过的字符及其出现的索引位置。
这里定义128长度的数组的方式更通用一点。
为什么是128长度的数组呢?因为标准ASCII码表就是128个字符,每个字符都对应一个码值,码值范围是0~127,而这刚好对应上了128长度的数组索引。
R 指针向右移动扫描时,每当扫描到一个字符 c,我们就去index中查找,该字符上次出现的位置index[c]:
- 如果 index[c] >= L,则说明当前 R 指针扫描到的字符 c 在当前滑窗内已经存在,其位置在 index[c] 处(L ≤ index[c] < R):
- 此时 [L, R-1] 范围子串就是一个不含重复字符的子串,记录此时符合要求的子串长度:(R - 1) - L + 1,保留最大的长度
- 为了保证滑窗内不含有重复字符,我们应该将移动 L = index[c] + 1,这样的话,滑窗内部就不会含有重复的 c 了
- 如果 index[c] < L,则说明当前 R 指针扫描的字符 c 并没有造成滑窗内产生重复字符
然后更新 index[c] = R,以及R++继续扫描。
按照上面步骤,我们仅需要遍历一轮字符串s,即可得到题解。
C源码实现
int lengthOfLongestSubstring(char* s) {
// index数组用于记录s串中字符c出现的索引位置
int index[128]; // 标准ASCII码表有128个基本字符,对应的ASCII码值是0~127
for (int c = 0; c < 128; c++) {
index[c] = -1;
}
// 滑窗范围 [l, r)
int l = 0;
int r = 0;
// 记录最大长度
int maxLen = 0;
// 遍历s串的每个字符c
while (s[r] != '\0') {
char c = s[r];
// 当前r指针指向的字符c, 如果在滑窗内存在了,则index[c] >= l
if (index[c] >= l) {
// 此时 [l, r) 左闭右开范围是一个无重复字符的子串, 子串长度为:(r - l) - l + 1
maxLen = (int)fmax(maxLen, r - l);
// 保证滑窗范围内无重复字符,则移动 l 到滑窗内重复字符c的右侧位置
l = index[c] + 1;
}
// 纳入r指针
index[c] = r;
r++;
}
// 收尾处理
return (int)fmax(maxLen, r - l);
}
C++源码实现
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// index数组用于记录s串中字符c出现的索引位置
// 标准ASCII码表有128个基本字符,对应的ASCII码值是0~127
vector<int> index(128, -1);
// 滑窗范围 [l, r)
int l = 0;
int r = 0;
// 记录最大长度
int maxLen = 0;
// 遍历s串的每个字符c
while (r < s.length()) {
char c = s[r];
// 当前r指针指向的字符c, 如果在滑窗内存在了,则index[c] >= l
if (index[c] >= l) {
// 此时 [l, r) 左闭右开范围是一个无重复字符的子串, 子串长度为:(r - l) - l + 1
maxLen = max(maxLen, r - l);
// 保证滑窗范围内无重复字符,则移动 l 到滑窗内重复字符c的右侧位置
l = index[c] + 1;
}
// 纳入r指针
index[c] = r;
r++;
}
// 收尾处理
return max(maxLen, r - l);
}
};
Java源码实现
class Solution {
public int lengthOfLongestSubstring(String s) {
// index数组用于记录s串中字符c出现的索引位置
int[] index = new int[128]; // 标准ASCII码表有128个基本字符,对应的ASCII码值是0~127
Arrays.fill(index, -1);
// 滑窗范围 [l, r)
int l = 0;
int r = 0;
// 记录最大长度
int maxLen = 0;
// 遍历s串的每个字符c
while (r < s.length()) {
char c = s.charAt(r);
// 当前r指针指向的字符c, 如果在滑窗内存在了,则index[c] >= l
if(index[c] >= l) {
// 此时 [l, r) 左闭右开范围是一个无重复字符的子串, 子串长度为:(r - l) - l + 1
maxLen = Math.max(maxLen, r - l);
// 保证滑窗范围内无重复字符,则移动 l 到滑窗内重复字符c的右侧位置
l = index[c] + 1;
}
// 纳入r指针
index[c] = r;
r++;
}
// 收尾处理
return Math.max(maxLen, r - l);
}
}
Python源码实现
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
# index字典用于记录s串中字符c出现的索引位置
index = {}
# 滑窗范围 [l, r)
l = 0
r = 0
# 记录最大长度
maxLen = 0
# 遍历s串的每个字符c
while r < len(s):
c = s[r]
# 当前r指针指向的字符c, 如果在滑窗内存在了,则index[c] >= l
if c in index and index[c] >= l:
# 此时 [l, r) 左闭右开范围是一个无重复字符的子串, 子串长度为:(r - l) - l + 1
maxLen = max(maxLen, r - l)
# 保证滑窗范围内无重复字符,则移动 l 到滑窗内重复字符c的右侧位置
l = index[c] + 1
# 纳入r指针
index[c] = r
r += 1
# 收尾处理
return max(maxLen, r - l)
JavaScript源码实现
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function (s) {
// index数组用于记录s串中字符c出现的索引位置
const index = new Map();
// 滑窗范围 [l, r)
let l = 0;
let r = 0;
// 记录最大长度
let maxLen = 0;
// 遍历s串的每个字符c
while (r < s.length) {
const c = s[r];
// 当前r指针指向的字符c, 如果在滑窗内存在了,则index[c] >= l
if (index.has(c) && index.get(c) >= l) {
// 此时 [l, r) 左闭右开范围是一个无重复字符的子串, 子串长度为:(r - l) - l + 1
maxLen = Math.max(maxLen, r - l);
// 保证滑窗范围内无重复字符,则移动 l 到滑窗内重复字符c的右侧位置
l = index.get(c) + 1;
}
// 纳入r指针
index.set(c, r++);
}
// 收尾处理
return Math.max(maxLen, r - l);
};