目录
[示例 1:](#示例 1:)
[示例 2:](#示例 2:)
[示例 3:](#示例 3:)
[解法一:暴力枚举 + 哈希表(O(n²))](#解法一:暴力枚举 + 哈希表(O(n²)))
OJ链接:无重复字符的最长子串
哈喽大家好!今天咱们来啃一道经典的算法题------无重复字符的最长子串。这道题在 LeetCode 上是第 3 题,难度中等,但绝对是必练的滑动窗口入门题!
一、题目解析
先看题目描述:
给定一个字符串
s,请你找出其中不含有重复字符 的最长子串的长度。
注意关键词:子串 (必须是连续的!)和无重复字符。
示例 1:
-
输入:
s = "abcabcbb" -
输出:3
-
解释:因为无重复字符的最长子串是
"abc",所以长度为 3。
示例 2:
-
输入:
s = "bbbbb" -
输出:1
-
解释:因为无重复字符的最长子串是
"b",所以长度为 1。
示例 3:
-
输入:
s = "pwwkew" -
输出:3
-
解释:因为无重复字符的最长子串是
"wke",所以长度为 3。(注意"pwke"是子序列,不是子串!)
📌 小贴士:子串 vs 子数组 → 都是连续的一段!别被"子序列"带偏了!
二、算法原理
这道题有几种解法,我们重点讲两种:
解法一:暴力枚举 + 哈希表(O(n²))
-
枚举所有子串,用哈希表判断是否有重复字符。
-
时间复杂度高,不推荐,但可以作为理解题意的起点。
解法二:滑动窗口(推荐!O(n))
-
利用"窗口"思想,维护一个++无重复字符的连续区间。++
-
用两个指针
left和right表示窗口左右边界。 -
用一个数组(或哈希表)记录每个字符出现的次数。
滑动窗口的核心步骤:
-
初始化 :
left = 0,right = 0 -
进窗口 :让
s[right]进入窗口 →hash[s[right]]++ -
判断 :如果
hash[s[right]] > 1→ 说明有重复! -
出窗口 :从左边开始删,直到重复字符被移除 →
hash[s[left++]]-- -
更新结果 :
ret = Math.max(ret, right - left + 1) -
移动右指针 :
right++
关键点:窗口内始终是无重复字符的!一旦重复,就收缩左边界,直到恢复无重复状态。
三、编写代码(Java)
我整理了两种写法,for循环写法更推荐(结构清晰,易读),也保留了while循环写法,方便你对比学习。
for循环写法(推荐!!!)
java
class Solution {
public int lengthOfLongestSubstring(String ss) {
// 先转换为字符数组
char[] s = ss.toCharArray();
// 数组模拟哈希表存储每个字符出现的次数
// 也是滑动窗口需要维护的数据
int hash[] = new int[128];
int n = ss.length();
int len = 0;
for(int left = 0, right = 0; right < n; right++) {
// 入窗口
hash[s[right]]++;
// 判断
while(hash[s[right]] > 1) {
// 出窗口
hash[s[left++]]--;
}
// 更新结果
len = Math.max(len, right - left + 1);
}
return len;
}
}
while循环写法
java
class Solution {
public int lengthOfLongestSubstring(String ss) {
char[] s = ss.toCharArray();
int[] hash = new int[128];
int left = 0, right = 0;
int ret = 0;
int n = ss.length();
while (right < n) {
// 入窗口
hash[s[right]]++;
// 判断
while (hash[s[right]] > 1) {
// 出窗口
hash[s[left++]]--;
}
// 更新结果
ret = Math.max(ret, right - left + 1);
// 让下一个字符进入窗口
right++;
}
return ret;
}
}
四、总结
这道题是滑动窗口的经典入门题,核心是:
-
用双指针维护窗口
-
用数组/哈希表记录字符频次
-
遇到重复就收缩左边界
-
每次更新最大长度
时间复杂度:O(n)
空间复杂度:O(1)(因为字符集固定128)