题目理解:
子串是指一个字符串中连续的一部分字符序列。
特点:
-
连续性:子串必须由相邻的字符组成,不能跳过任何字符。
例如,在字符串 "abcde"
中:
子串:"abc"
、"bcd"
、"e"
。
不是子串:"ace"
(因为字符不是连续的)。
-
包含关系:子串一定是字符串的一部分,但字符串本身也是自己的子串。
-
位置无关:子串可以从任意位置开始,长度可以为 1(单个字符)到字符串本身的长度。
任意字符串都一定有子串,因为子串可以是整个字符串本身,也可以是它的一部分,就算是空字符串也算一个子串。所以,只要对字符串进行遍历,去掉重复的字符,就能找到字符串的子串。
思路一(暴力解法):
- 遍历字符串的每个字符。
- 每次遇到一个字符时,检查它前面的部分(它左边的字符)有没有重复。
- 如果没有重复,就继续往后看,并记录当前最长的连续不重复的字符长度。
- 如果发现有重复字符,就从重复字符的下一个位置重新开始计算新的子串。
- 最后,把找到的最长长度返回
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 如果字符串为空,直接返回 0
if (s.empty()) {
return 0;
}
// 初始化变量
int maxLen = 0; // 记录最长无重复子串的长度
int len = 0; // 当前子串长度
// 遍历字符串
for (int i = 0; i < s.size(); ++i) {
// 检查当前字符在当前子串中是否重复
for (int j = i - len; j < i; ++j) { // 遍历当前子串的范围
if (s[j] == s[i]) { // 如果字符重复
len = i - j - 1; // 当前子串长度更新为从重复字符后一位开始
break;
}
}
// 更新当前子串长度
len++;
// 更新最大长度
maxLen = max(maxLen, len);
}
return maxLen; // 返回最长子串的长度
}
};
但是该方法的时间复杂度高达O(N²) ,下面是优化后的算法
思路二 (滑动窗口法):
- 通过动态调整 左指针 和 右指针 的位置,维护窗口的范围。
- 窗口中的状态通过
unordered_set
实时更新,保证窗口内的字符无重复。 - 左指针负责收缩窗口(移除字符),右指针负责扩展窗口(加入字符)。
- 每次调整窗口后,更新最长子串的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//设置一个只包含值的哈希表,用于记录滑动窗口中的字符
unordered_set<char> occur;
int n = s.size();
//设置滑动窗口的右边界索引
//初始值设为 -1,相当于右边界在字符串的左侧,尚未进入字符串
int right = -1;
int lens = 0;
//遍历字符串的每个字符,将其暂时作为滑动窗口的左边界遍历字符串
for(int i = 0; i < n; i++){
//收缩滑动窗口左边界
if(i != 0){
occur.erase(s[i - 1]);
}
//移动右指针,扩充滑动窗口
while(right + 1 < n && !occur.count(s[right +1])){
occur.insert(s[right + 1]);
++right;
}
lens = max(lens, right - i + 1);
}
return lens;
}
};
该方法的时间复杂度高达O(N) ,是解决该问题的最优方法
在复现代码的过程中发现了更符合正常思路的代码,同样使用滑动窗口方法,现更新如下:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//从字符串的第一个字符开始遍历字符串,借助一个哈希表暂存滑动窗口内的字符
//如果待遍历的字符已存在于哈希表中,更新滑动窗口左边界以及内部元素
//否则就将该字符加入哈希表中
unordered_set<char> window;
int len = 0;
int right = 0;
for(int i = 0; i < s.size(); i++){
//扩充滑动窗口,如果不含有重复字符,就将其加入滑动窗口内部
while(right < s.size() && !window.count(s[right])){
window.insert(s[right]);
right++;
}
//更新滑动窗口内部元素
window.erase(s[i]);
//更新最大长度
len = max(len, right - i);
}
return len;
}
};