写在前面
这道题不要说是特斯拉,可能放眼所有存在"算法笔面"环节的互联网公司,也是标准 Easy 水平。
以至于遇到该题目的同学都有"准备过于充分"的感觉: 🤣
题目描述
平台:LeetCode
题号:3
给定一个字符串,请你找出其中不含有重复字符的「最长子串」的长度。
示例 1:
ini
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
ini
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
makefile
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
ini
输入: s = ""
输出: 0
提示:
- <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 < = s . l e n g t h < = 5 × 1 0 4 0 <= s.length <= 5 \times 10^4 </math>0<=s.length<=5×104
s
由英文字母、数字、符号和空格组成
滑动窗口
定义两个指针 start
和 end
, [start:end]
表示当前处理到的子串。
子串 [start:end]
始终满足要求:无重复字符。
从前往后进行扫描 s
,用哈希表记录 [start:end]
中每字符的出现次数。
遍历过程中,end
不断自增,将第 end
个字符在哈希表中出现的次数加一。
令 right
为下标 end
对应的字符,当满足 map.get(right) > 1
时,代表此前出现过第 end
位对应的字符。
此时更新 start
的位置(使其右移),直到不满足 map.get(right) > 1
(代表 [start:end]
恢复满足无重复字符的条件),再用 [start:end]
长度更新答案。
Java 代码:
Java
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int ans = 0;
for (int start = 0, end = 0; end < s.length(); end++) {
char right = s.charAt(end);
map.put(right, map.getOrDefault(right, 0) + 1);
while (map.get(right) > 1) {
char left = s.charAt(start);
map.put(left, map.get(left) - 1);
start++;
}
ans = Math.max(ans, end - start + 1);
}
return ans;
}
}
Python 代码:
Python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
map = {}
ans = 0
start = 0
for end in range(len(s)):
right = s[end]
map[right] = map.get(right, 0) + 1
while map[right] > 1:
left = s[start]
map[left] = map[left] - 1
start += 1
ans = max(ans, end - start + 1)
return ans
C++ 代码:
C++
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> map;
int ans = 0;
for (int start = 0, end = 0; end < s.length(); end++) {
char right = s[end];
map[right] = map[right] + 1;
while (map[right] > 1) {
char left = s[start];
map[left] = map[left] - 1;
start++;
}
ans = max(ans, end - start + 1);
}
return ans;
}
};
TypeScript 代码:
TypeScript
function lengthOfLongestSubstring(s: string): number {
const map: { [key: string]: number } = {};
let ans = 0;
let start = 0;
for (let end = 0; end < s.length; end++) {
const right = s.charAt(end);
map[right] = (map[right] || 0) + 1;
while (map[right] > 1) {
const left = s.charAt(start);
map[left] = (map[left] || 0) - 1;
start++;
}
ans = Math.max(ans, end - start + 1);
}
return ans;
};
- 时间复杂度:虽然有两层循环,但每个字符在哈希表中最多只会被插入和删除一次,复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)
- 空间复杂度:使用了哈希表进行字符记录,复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)
总结
现在看来这道题确实简单到离谱。
但在 LeetCode 平台中,这道题的难度标签却是「中等」。
其实从 3 这样较小的题号就能发现,这是属于最早期的那批题目。
说明在那个算法笔面刚出现,甚至是 LeetCode 刚建站,总共只有 150 道题目的那个年代,像「滑动窗口」这样的知识点,还不被大家所掌握,绝大多数只能给出双层循环的 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2) 解法。
反观现在的笔试面试,一些在招聘市场"供过于求"的公司,有时候还会把网络流搬上桌面 ...
可见,算法内卷的道路只会放缓,不会停止,没有尽头。
更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉