LeetCode LCR 016. 无重复字符的最长子串 (Java)
LeetCode LCR 016. 无重复字符的最长子串
滑动窗口法解「无重复字符的最长子串」
问题描述
给定一个字符串 s
,找出其中不含有重复字符的 最长连续子字符串 的长度。
示例
输入:s = "abcabcbb"
输出:3
(最长子串为 "abc"
)
解题思路
滑动窗口法(双指针)
-
窗口定义:用左右指针
left
和right
表示当前窗口的左右边界,窗口内字符不重复。 -
核心操作:
• 扩展右边界:右指针不断右移,直到遇到重复字符。• 收缩左边界:一旦发现重复,左指针右移一位,移除原左边界字符。
-
去重机制:通过哈希集合
Set
实时记录窗口内的字符,确保窗口的唯一性。
算法流程
步骤详解
-
初始化:
• 左指针left = 0
,右指针right = 0
• 哈希集合
Set
存储窗口字符• 结果变量
res
记录最大长度 -
滑动窗口扩展:
for (int left = 0; left < s.length(); left++) {// 右指针扩展至出现重复或越界while (right < s.length() && !set.contains(s.charAt(right))) {set.add(s.charAt(right));right++;}// 更新最大长度res = Math.max(res, right - left);// 左指针右移一位,移除原左边界字符set.remove(s.charAt(left)); }
-
动态调整:
• 每次左指针右移后,右指针从当前位置继续扩展,无需回溯。
复杂度分析
• 时间复杂度:O(n),左右指针各遍历一次字符串。
• 空间复杂度:O(1),字符集大小固定(ASCII 最多 128 种字符)。
代码实现
public static int lengthOfLongestSubstring(String s) {int right = 0; // 窗口右边界int res = 0; // 最长子串长度Set<Character> set = new HashSet<>();for (int left = 0; left < s.length(); left++) {// 右指针扩展至出现重复或越界while (right < s.length() && !set.contains(s.charAt(right))) {set.add(s.charAt(right));right++;}// 更新最大长度res = Math.max(res, right - left);// 左指针右移一位,移除原左边界字符set.remove(s.charAt(left));}return res;
}
力扣通过截图
示例解析
示例 1:s = "abcabcbb"
-
初始状态:
left=0
,right=0
• 窗口扩展至right=3
(字符a,b,c
),此时res=3
-
左指针右移:
left=1
,移除a
• 窗口扩展至right=4
(加入a
失败,窗口变为b,c,a
,res=3
-
循环结束:最终
res=3
关键点说明
为何时间复杂度是 O(n)?
• 每个字符最多被左右指针各访问一次,无重复操作。
为何用 Set
而不用 Map
?
• Set
直接检查是否存在重复,无需记录索引,节省空间。
如何处理中间重复字符?
• 左指针每次仅移动一位,逐步缩小窗口,确保窗口内始终无重复。
拓展思考
优化版本(跳跃式收缩)
若记录字符的最新位置,左指针可跳跃式移动:
Map<Character, Integer> map = new HashMap<>();
int left = 0;
for (int right = 0; right < s.length(); right++) {if (map.containsKey(s.charAt(right))) {left = Math.max(left, map.get(s.charAt(right)) + 1);}map.put(s.charAt(right), right);res = Math.max(res, right - left + 1);
}
• 优势:减少左指针移动次数,提升效率。
力扣通过截图
通过滑动窗口法,我们高效地解决了子串查重问题,平衡了时间与空间复杂度。此方法在字符串处理中具有广泛的应用场景。