一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
问题描述
给定一个字符串 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 由英文字母、数字、符号和空格组成
剖析
暴力破解
笔者第一反应能想到的就是暴力破解,利用两层循环进行比对是否重复,如果重复就从下一个字符开始往后继续比对,期间可以使用HashSet进行查看是否存在相同的字符,当从下一个开始时记得把上一个字符给移除掉。
这里代码就不写了,我们来看下它的复杂度:
- 时间复杂度:O(n^2),两层循环。
- 空间复杂度:HashSet最长时应该就是最长字串的长度,设长度为M,那么就是O(M)。
滑动窗口
由于笔者刚写完《滑动窗口最大值》这道题,所以笔者很容易想到滑动窗口,但是这个很明显是动态的,当遇到重复的就缩小(继续往下一个字符走,滑过的移除),遇到不重复的扩张,这滑动的时候我们需要记录当前不重复的子字符串长度,每次都取较大的最后滑完就是“最长的不重复子字符串”的长度。
下面我们描述下具体步骤:
- 从第一个字符开始,把第一个字符装进HashSet中。
- 扩大滑块把下一个字符和HashSet中的已存在的字符进行对比。
- 如果在HashSet中不存在就把这个字符放入HashSet中,更新较大“不重复子字符串”长度,继续重复第二三步骤。如果发现存在就滑动滑块,把滑过去的字符从HashSet中移除,然后重复第二三步骤。
复杂度:
- 时间复杂度:很明显滑块要从头滑到尾,所以复杂度为O(n)。
- 空间复杂度:应该和暴力破解中的一样为O(M)。
下面我们直接看代码。
代码
public static int lenghtOfLongestSubstring(String s) {
int result = 0;
Set<Character> characterSet = new HashSet<>();
int rt = 0;
for (int i = 0; i < s.length(); i++) {
if (i > 0) {
characterSet.remove(s.charAt(i - 1));
}
while (rt < s.length() && !characterSet.contains(s.charAt(rt))) {
characterSet.add(s.charAt(rt));
rt++;
}
result = Math.max(result, characterSet.size());
}
return result;
}