大纲
- 题目
- 地址
- 内容
- 解题
- 代码地址
题目
地址
https://leetcode.com/problems/longest-substring-without-repeating-characters/description/
内容
Given a string s, find the length of the longest substring without repeating characters.
Example 1:
Input: s = “abcabcbb”
Output: 3
Explanation: The answer is “abc”, with the length of 3.
Example 2:
Input: s = “bbbbb”
Output: 1
Explanation: The answer is “b”, with the length of 1.
Example 3:
Input: s = “pwwkew”
Output: 3
Explanation: The answer is “wke”, with the length of 3.
Notice that the answer must be a substring, “pwke” is a subsequence and not a substring.
Constraints:
- 0 <= s.length <= 5 * 104
- s consists of English letters, digits, symbols and spaces.
解题
这题是要在一个只包含English letters, digits, symbols and spaces的字符串中找打最长的不重复子串。
一种比较容易想到的思路是:遇到重复的字符,则从上次这个字符出现的位置的后个位置开始尝试比较。比如abcdef是当前最长不重复子串。如果后面出现的字符是b,则从下标为1的b的后一位(c)开始组成新的测试子串,即cdefb。当新的测试子串的长度大于当前最长的子串,则更新该最大长度值。
为了判断后续出现的字符是否已经存在于当前最长子串中,则可以考虑使用map<char,int>的结构来存储最长子串及其坐标信息。但是这个题目存在只包含English letters, digits, symbols and spaces的条件,这意味着每个字符都是ASCII码中的字符——它只有128个——且是连续的数字(见https://baike.baidu.com/item/ASCII/309296)。我们可以充分利用这个条件,使用一个更加紧凑高效的结构来存储这个关系。
array<int, 128> charIndex;
charIndex.fill(-1);
然后我们遍历整个字符串,将这样的关系存储起来。
for (int i = 0; i < s.size(); i++) {
……
charIndex[int(s[i])] = i;
……
}
这儿有一个问题:在寻找过程中,如果最长子串发生了变化,则被剔除出的字符串是否应该从charIndex中剔除?比如上例abcdef是最长子串时,charIndex包含这些字符以及它们的位置。如果在f后面新出现的字符是b,则要开始新探索的子串是cdefb,这个时候字符a是否需要从charIndex中剔除?因为它可能会影响到后续再次出现a时的判断。实际我们通过新探索的子串开头第一个字符位置来判断新的重复是否需要处理。在这个例子中,第一个a(绿色)位于新子串cdefb起始位置(箭头处)的左侧,即使它存在charIndex中,也不会影响后续的探索,即我们可将新的探索子串定义为cdefba。假设下一个出现的是字符d,由于上一次出现的d位于子串起始位置(箭头处)的右侧,所以需要重新探索新的子串
经过分析,我们知道子串起始位置也是需要记录,以用于辅助判断是否要使用新子串。
由于该题只是要输出最长子串长度,并不要求输出最长子串的内容,所以我们并不用记录子串的具体顺序,只要比较并保留最长的一条串的长度即可。
最长串的比较maxLen = max(maxLen, i - start)
可以优化到串的起始位置发生变化的时候进行。这样可以将对比的次数降到最低。
#include <string>
#include <array>
using namespace std;
class Solution {
public:
int lengthOfLongestSubstring(string s) {
array<int, 128> charIndex;
charIndex.fill(-1);
int start = 0;
int maxLen = 0;
for (int i = 0; i < s.size(); i++) {
if (charIndex[int(s[i])] >= start) {
maxLen = max(maxLen, i - start);
start = charIndex[int(s[i])] + 1;
}
charIndex[int(s[i])] = i;
}
if (s.size() - start > maxLen) {
maxLen = s.size() - start;
}
return maxLen;
}
};
上述代码的一个边界问题是:最长子串的尾部正好是原始串的尾部。这样我们必须在遍历结束后,考虑这种情况下最长子串的可能性。
代码地址
https://github.com/f304646673/leetcode/tree/main/3-Longest-Substring-Without-Repeating-Characters