原理:
定义左右两个指针,保证两个指针对应的子串中没有重复的字符,寻找并记录最长的子串长度。如果窗口满足条件,右指针向右滑动扩大窗口,更新最优值;如果窗口不满足条件,左指针向右缩小窗口。 (这里需要借助数据结构 -- 哈希集合,来判断是否有重复字符)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目:
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
示例:
输入:nums= [-1,0,3,5,9,12],target=9 输出: 4
提示:
- 你可以假设
nums
中的所有元素是不重复的。n
将在[1, 10000]
之间。
nums
的每个元素都将在[-9999, 9999]
之间。
思路1:滑动窗口
将左指针向右移动一格,表示我们开始枚举下一个字符作为起始位置,然后我们可以不断地向右移动右指针,但需要保证这两个指针对应的子串中没有重复的字符。在移动结束后,这个子串就对应着以左指针开始的,不包含重复字符的最长子串,我们记录下这个子串的长度。在枚举结束后,我们找到的最长的子串的长度即为答案。
在上面的流程中,我们还需要使用一种数据结构来判断是否有重复的字符,常用的数据结构为哈希集合(即 C++ 中的 std::unordered_set,Java 中的 HashSet,Python 中的 set, JavaScript 中的 Set)。
在左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往哈希集合中添加一个字符。
答案:
① 方法1:暴力求解
逐个生成子字符串,看它是否不含有重复的字符 。
该方法虽然可以实现,但是不够优雅。 【208 ms 47.31 MB】
var lengthOfLongestSubstring = function (s) {
// 左右指针
let left = (right = max = 0);
let val = (endVal = "");
let length = s.length;
while (right < length) {
endVal = s.charAt(right);
if (val.indexOf(endVal) < 0) {
val = val + endVal;
// 右指针向右移动
right++;
if (right >= length) {
max = max > val.length ? max : val.length;
}
} else {
max = max > val.length ? max : val.length;
left++;
right = left + 1;
val = s.charAt(left);
}
}
return max;
};
console.log("答案", lengthOfLongestSubstring("abcabcbb")); // 3
console.log("答案", lengthOfLongestSubstring("")); // 0
console.log("答案", lengthOfLongestSubstring(" ")); // 1
console.log("答案", lengthOfLongestSubstring(" ")); // 1
console.log("答案", lengthOfLongestSubstring("aa")); // 1
console.log("答案", lengthOfLongestSubstring("ab")); // 2
console.log("答案", lengthOfLongestSubstring("pwwkew")); // 3
② 方法二:滑动窗口及优化
关键字:重复字符 --> 出现1次
模式识别1:一旦涉及出现次数,就需要用到散列表
构造子串,散列表存下标;
模式识别2:涉及子串,考虑滑动窗口
答案1:官方题解【96 ms 45.49 MB】
var lengthOfLongestSubstring = function (s) {
// 哈希集合,记录每个字符是否出现过
const occ = new Set();
const length = s.length;
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
let start = -1,
res = 0;
for (let i = 0; i < length; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.delete(s.charAt(i - 1));
}
while (start + 1 < length && !occ.has(s.charAt(start + 1))) {
// 不断地移动右指针
occ.add(s.charAt(start + 1));
++start;
}
// 第 i 到 start 个字符是一个极长的无重复字符子串
res = Math.max(res, start - i + 1);
}
return res;
};
console.log("答案", lengthOfLongestSubstring("abcabcbb")); // 3
console.log("答案", lengthOfLongestSubstring("")); // 0
console.log("答案", lengthOfLongestSubstring(" ")); // 1
console.log("答案", lengthOfLongestSubstring(" ")); // 1
console.log("答案", lengthOfLongestSubstring("aa")); // 1
console.log("答案", lengthOfLongestSubstring("ab")); // 2
console.log("答案", lengthOfLongestSubstring("pwwkew")); // 3
答案2:个人【88 ms 45.73 MB】
var lengthOfLongestSubstring = function (s) {
// 定义左指针、右指针、长度、最大长度
let left = (right = length = maxLength = 0)
// 定义set集合
let set = new Set()
// 遍历字符串
while (right < s.length) {
if (!set.has(s[right])) {
// 不存在相同字符,把当前字符添加到字符集中
set.add(s[right])
// 更新长度
length++
if (length > maxLength) {
maxLength = length
}
// 右指针往右移动
right++
} else {
// 删除字符集中的第一个字符
set.delete(s[left])
// 更新长度
length--
// 左指针往右移动
left++
}
}
return maxLength
};
答案3:视频【68 ms 45.91 MB】
var lengthOfLongestSubstring = function (s) {
// 定义左指针、右指针、长度、最大长度
let left = (right = length = maxLength = 0)
// 定义set集合
let set = new Set()
// 遍历字符串
while (right < s.length) {
if (!set.has(s[right])) {
// 不存在相同字符,把当前字符添加到字符集中
set.add(s[right])
// 更新长度
length++
if (length > maxLength) {
maxLength = length
}
// 右指针往右移动
right++
} else {
while (set.has(s[right])) {
// 删除字符集中的第一个字符
set.delete(s[left])
// 更新长度
length--
// 左指针往右移动
left++
}
set.add(s[right])
length++
right++
}
}
return maxLength
};
视频:
一道算法题理解滑动窗思想!【趣刷Leetcode】 No.3 无重复字符的最长子串_哔哩哔哩_bilibili