文章目录
- 1. 题目描述
- 2. 理解题目
- 3. 解法一:暴力法
- 3.1 思路
- 3.2 Java代码实现
- 3.3 代码详解
- 3.4 复杂度分析
- 3.5 问题与改进
- 4. 解法二:滑动窗口法
- 4.1 思路
- 4.2 Java代码实现
- 4.3 代码详解
- 4.4 复杂度分析
- 4.5 优化与技巧
- 5. 解法三:优化的滑动窗口法
- 5.1 思路
- 5.2 Java代码实现
- 5.3 代码详解
- 5.4 复杂度分析
- 5.5 进一步优化
- 6. 解法四:使用位图的优化方法
- 6.1 思路
- 6.2 Java代码实现
- 6.3 代码详解
- 6.4 复杂度分析
- 7. 特殊情况和边界处理
- 8. 性能对比与推荐
- 9. 完整的 Java 解决方案
- 10. 实例讲解
- 11. 测试用例
- 12. 图解算法
- 13. 常见问题与解答
- 13.1 为什么滑动窗口比暴力法更高效?
- 13.2 如何处理Unicode字符?
- 13.3 为什么使用`Math.max(start, map.get(c) + 1)`而不是简单地`map.get(c) + 1`?
- 13.4 这个问题有其他解法吗?
- 14. 总结与技巧
- 14.1 解题要点
- 14.2 常用技巧
- 14.3 面试技巧
- 15. 相关问题
- 16. 参考资料
1. 题目描述
给定一个字符串 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
由英文字母、数字、符号和空格组成
2. 理解题目
这道题要求我们找出给定字符串中不包含重复字符的最长子串的长度。我们需要理解以下几点:
- 子串 vs 子序列:子串是原字符串中连续的字符序列,而子序列可以是原字符串中不连续的字符序列。本题求的是子串。
- 无重复字符:子串中不能有重复出现的字符。
- 最长:如果有多个无重复字符的子串,我们只关心最长的那个。
关键点:
- 我们需要判断子串中是否有重复字符
- 我们需要记录遇到的每个字符
- 我们需要计算无重复字符子串的长度,并找出最大值
3. 解法一:暴力法
3.1 思路
最直观的方法是检查所有可能的子串,判断它们是否包含重复字符,然后找出最长的。
具体算法:
- 遍历字符串的所有可能的子串
- 对于每个子串,检查是否包含重复字符
- 如果不包含重复字符,更新最大长度
3.2 Java代码实现
public class Solution {public int lengthOfLongestSubstring(String s) {int n = s.length();int maxLength = 0;// 遍历所有可能的子串起点for (int i = 0; i < n; i++) {// 遍历所有可能的子串终点for (int j = i; j < n; j++) {// 检查子串s[i...j]是否包含重复字符if (allUnique(s, i, j)) {maxLength = Math.max(maxLength, j - i + 1);}}}return maxLength;}// 检查子串s[start...end]是否包含重复字符private boolean allUnique(String s, int start, int end) {Set<Character> set = new HashSet<>();for (int i = start; i <= end; i++) {char ch = s.charAt(i);// 如果字符已存在于集合中,则包含重复字符if (set.contains(ch)) {return false;}set.add(ch);}return true;}
}
3.3 代码详解
- 我们使用两个嵌套循环来遍历所有可能的子串:
- 外循环变量
i
表示子串的起始位置 - 内循环变量
j
表示子串的结束位置
- 外循环变量
- 对于每个子串
s[i...j]
,我们调用allUnique
方法来检查它是否包含重复字符 allUnique
方法使用Set
来跟踪已经出现过的字符:- 如果遍历过程中发现字符已存在于集合中,则返回
false
- 否则,将字符添加到集合中,并继续检查下一个字符
- 如果遍历完成没有发现重复字符,则返回
true
- 如果遍历过程中发现字符已存在于集合中,则返回
- 如果子串不包含重复字符,我们更新最大长度
maxLength
3.4 复杂度分析
- 时间复杂度:O(n³),其中n是字符串的长度
- 外循环执行n次
- 内循环最多执行n次
allUnique
方法需要O(j-i)的时间,最坏情况下是O(n)- 总时间复杂度为O(n²) * O(n) = O(n³)
- 空间复杂度:O(k),其中k是字符集的大小。在最坏的情况下,
allUnique
方法中的Set
将存储所有可能的字符。
3.5 问题与改进
暴力法简单直观,但效率很低。对于每个子串,我们都要重新检查所有字符是否有重复,这导致了许多重复计算。我们可以通过滑动窗口方法来优化。
4. 解法二:滑动窗口法
4.1 思路
滑动窗口是一个很常用的双指针技巧,我们可以用它来解决这个问题。窗口的左右边界表示当前正在考虑的子串的范围。
具体算法:
- 使用两个指针(左指针和右指针)表示一个窗口
- 右指针不断向右移动,扩大窗口,并将字符加入到集合中
- 当遇到重复字符时,左指针向右移动,缩小窗口,直到重复的字符被移出窗口
- 在整个过程中,维护窗口的最大长度
4.2 Java代码实现
public class Solution {public int lengthOfLongestSubstring(String s) {int n = s.length();Set<Character> set = new HashSet<>();int maxLength = 0;int left