题目描述
本文是LC第3题:无重复字符的最长子串。
题目描述如下
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
举例
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
解题思路
子串:连续的。比如 adbcdf
中,adb
、bcd
都是子串,但af
不是子串,因为违背了连续性。
无重复:也就是子串中的字符不能重复。
暴力解题
直接上手,我们很容易想出暴力解题的思路。
- 定义两个指针,初始都指向下标 0;
- 每次向右移动 R指针,直到R指针指向的当前元素与 arr[L~R-1] 之间的元素发生重复,则记录下当前 arr[L ~ R-1] 的子串长度,然后右移动L指针;
- 然后继续步骤2。
可以写出如下伪码
int l = 0, r = 0, max = 1;
while (r < n) {
if (is_exits(arr[r], arr, l, r-1) == true) { // arr[r] 与 arr[l ~ r-1] 之间的某个元素相同
if (r - l > max) max = r - l; // 记录下当前子串的长度
l++; // l右移,继续判断
}
}
而 is_exits
必定是一个循环,因此,这样的算法写出来就是双层循环。
也可以采取哈希表来进行 is_exists
的判断,将双层循环替换为单层的,代价是空间复杂度变为O(n)。以下是采取哈希表的代码实现:
class Solution {
public:
unordered_map < char, int > hashTable;
int lengthOfLongestSubstring(string s) {
int maxLength = 0;
int i = 0;
for ( int j = 0; j < s.length ( ); ++j ) {
auto tmp = hashTable.find ( s [ j ] );
/*
if ( tmp == hashTable.end ( ) ) // s [ j ] 不在集合内
hashTable [ s [ j ] ] = j;
else {// s [ j ] 在 集合内
for ( int k = i; k < idx; ++k ) hashTable.erase ( s [ k ] ); // 删除集合里重复字符之前的元素
hashTable [ s [ j ] ] = j; // 改变重复字符的value值
i = tmp -> second + 1; // i 标定左边界
}
*/
if ( tmp != hashTable.end ( ) ) {
int idx = tmp -> second;
for ( int k = i; k < idx; ++k ) hashTable.erase ( s [ k ] );
i = idx + 1;
}
hashTable [ s [ j ] ] = j;
maxLength = max ( maxLength, j - i + 1 );
}
return maxLength;
}
};
优化解法
但此题,只要求我们求其长度,上面的做法将子串也同时求了出来,因此,我们再找找更简单的解法。
我们可以使用HashMap来建立字符和其出现位置之间的映射。
上面的暴力解法实际上是维护了一个滑动窗口,窗口内的都是没有重复的字符,我们需要尽可能的扩大窗口的大小。
而此题跟位置相关,由于窗口在不停向右滑动,所以我们只关心每个字符最后出现的位置,并建立映射。
窗口的右边界就是当前遍历到的字符的位置,为了求出窗口的大小,我们需要一个变量left来指向滑动窗口的左边界。
- 这样,如果当前遍历到的字符从未出现过,那么直接扩大右边界,
- 如果之前出现过,那么就分两种情况,在或不在滑动窗口内,
- 如果不在滑动窗口内,那么就没事,当前字符可以加进来,
- 如果在的话,就需要先在滑动窗口内去掉这个已经出现过的字符了,去掉的方法并不需要将左边界left一位一位向右遍历查找,由于我们的HashMap已经保存了该重复字符最后出现的位置,所以直接移动left指针就可以了。
我们维护一个结果res,每次用出现过的窗口大小来更新结果res,就可以得到最终结果了。
实现代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector<int> idx(256, -1); // 记录位置,用数组模拟哈希表,数组下标代表 256 个字符的 ascii码。
int start = -1; // 初始设置为 -1
int maxLength = 0;
for ( int i = 0; i < s.length(); ++i ) {
if ( idx[s[i]] > start ) {
start = idx[s[i]];
}
idx[s[i]] = i;
maxLength = max(maxLength, i-start); // 最大长度更新
}
return maxLength;
}
};
其中, idx[s[i]]
记录的是 s[i]
表示的字符最后一次出现在字符串中的位置。