面试经典算法题2 – 最长无重复子串
LeetCode.3
公众号:阿Q技术站
问题描述
给定一个字符串 s
,请你找出其中不含有重复字符的 最长连续子字符串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子字符串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子字符串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = ""
输出: 0
提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
思路
- 使用两个指针
left
和right
分别表示滑动窗口的左右边界,初始时都指向字符串的开头。 - 使用一个哈希表(或者数组)来记录字符是否出现过以及字符出现的位置。
- 不断移动右指针
right
,每次移动时判断当前字符是否出现过:- 如果当前字符已经出现过,并且在当前窗口内(即其位置大于等于
left
),则更新left
指针的位置为该字符上次出现的位置的下一个位置。 - 如果当前字符没有出现过,或者虽然出现过但不在当前窗口内,则更新最长子字符串的长度。
- 如果当前字符已经出现过,并且在当前窗口内(即其位置大于等于
- 不断更新最长子字符串的长度,并返回结果。
演示过程
- 初始化时,
left
和right
指针都指向字符串的开头:
哈希表:{a: 0}
- 移动右指针
right
,并使用哈希表记录字符出现的位置:
哈希表:{a: 0, b: 1}
- 继续移动右指针
right
:
哈希表:{a: 0, b: 1, c: 2}
- 在移动右指针
right
时,发现当前字符已经在哈希表中出现过,并且在当前窗口内(即其位置大于等于left
),因此更新left
指针的位置为该字符上次出现的位置的下一个位置,并更新最长子字符串的长度:
哈希表:{a: 3, b: 1, c: 2}
- 在移动右指针
right
时,发现当前字符已经在哈希表中出现过,并且在当前窗口内,因此更新left
指针的位置为该字符上次出现的位置的下一个位置,并更新最长子字符串的长度:
哈希表:{a: 3, b: 4, c: 2}
- 在移动右指针
right
时,发现当前字符已经在哈希表中出现过,并且在当前窗口内,因此更新left
指针的位置为该字符上次出现的位置的下一个位置,并更新最长子字符串的长度:
哈希表:{a: 3, b: 4, c: 5}
- 在移动右指针
right
时,发现当前字符已经在哈希表中出现过,并且在当前窗口内,因此更新left
指针的位置为该字符上次出现的位置的下一个位置,并更新最长子字符串的长度:
哈希表:{ b: 6, c: 5}
- 在移动右指针
right
时,发现当前字符已经在哈希表中出现过,并且在当前窗口内,因此更新left
指针的位置为该字符上次出现的位置的下一个位置,并更新最长子字符串的长度:
哈希表:{ b:7}
此时,left=right,退出循环。
参考代码:
C++
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> charIndex; // 哈希表用于记录字符出现的位置
int maxLength = 0; // 最长不含重复字符的子字符串的长度
int left = 0; // 滑动窗口的左边界
for (int right = 0; right < s.size(); ++right) {
char c = s[right];
if (charIndex.find(c) != charIndex.end() && charIndex[c] >= left) {
// 如果当前字符出现过,并且出现的位置在当前窗口内,则更新左边界
left = charIndex[c] + 1;
}
charIndex[c] = right; // 更新字符 c 的位置为 right
// 计算当前窗口的长度,并更新最长子字符串的长度
maxLength = max(maxLength, right - left + 1);
}
return maxLength; // 返回最长不含重复字符的子字符串的长度
}
int main() {
string s;
cout << "请输入一个字符串:" << endl;
cin >> s;
cout << "最长不含重复字符的连续子字符串的长度为:" << lengthOfLongestSubstring(s) << endl;
return 0;
}
Java
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static int lengthOfLongestSubstring(String s) {
Map<Character, Integer> charIndex = new HashMap<>(); // 哈希表用于记录字符出现的位置
int maxLength = 0; // 最长不含重复字符的子字符串的长度
int left = 0; // 滑动窗口的左边界
for (int right = 0; right < s.length(); ++right) {
char c = s.charAt(right);
if (charIndex.containsKey(c) && charIndex.get(c) >= left) {
// 如果当前字符出现过,并且出现的位置在当前窗口内,则更新左边界
left = charIndex.get(c) + 1;
}
charIndex.put(c, right); // 更新字符 c 的位置为 right
// 计算当前窗口的长度,并更新最长子字符串的长度
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength; // 返回最长不含重复字符的子字符串的长度
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String s = scanner.nextLine();
System.out.println("最长不含重复字符的连续子字符串的长度为:" + lengthOfLongestSubstring(s));
}
}
Python
def length_of_longest_substring(s):
char_index = {} # 字典用于记录字符出现的位置
max_length = 0 # 最长不含重复字符的子字符串的长度
left = 0 # 滑动窗口的左边界
for right, c in enumerate(s):
if c in char_index and char_index[c] >= left:
# 如果当前字符出现过,并且出现的位置在当前窗口内,则更新左边界
left = char_index[c] + 1
char_index[c] = right # 更新字符 c 的位置为 right
# 计算当前窗口的长度,并更新最长子字符串的长度
max_length = max(max_length, right - left + 1)
return max_length # 返回最长不含重复字符的子字符串的长度
if __name__ == "__main__":
s = input("请输入一个字符串:")
print("最长不含重复字符的连续子字符串的长度为:", length_of_longest_substring(s))