1、题干
给你一个字符串 s
,考虑其所有 重复子串 :即 s
的(连续)子串,在 s
中出现 2 次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果 s
不含重复子串,那么答案为 ""
。
示例 1:
输入:s = "banana"
输出:"ana"
示例 2:
输入:s = "abcd"
输出:""
提示:
2 <= s.length <= 3 * 104
s
由小写英文字母组成
卷不动了,滑动窗口交了
2、执行结果
3、解题思路
使用滑动窗口处理,如果当前窗口字符串长度大于已找到的最长子串长度,且在原字符串的末尾能找到另一个窗口字符串,则当前窗口字符串可以更新为最长子串;重复该动作直至窗口闭合。
- 最长重复子串记为
maxStr
,窗口左右边界分别记为i
和j
,窗口内的字符串记为curStr
- 当窗口
curStr
的长度大于maxStr
或者右边界j
越界时,窗口左边界右移i++
,并移除窗口curStr
起始位置的字母 - 当窗口
curStr
的长度不大于maxStr
且右边界j
未越界时- 在窗口
curStr
末尾追加右边界之后的字母s[j]
,并将窗口右边界右移j++
- 若窗口
curStr
的长度大于maxStr
且在字符串s
尾部找到另一个curStr
,则更新maxStr
- 在窗口
- 最后返回
maxStr
4、代码
var longestDupSubstring = function (s) {
let maxStr = '',curStr = '';
for (let i = 0, j = 0; i < s.length; i++) {
curStr = curStr.replace(s[i - 1], '');
while (curStr.length <= maxStr.length && j < s.length) {
curStr += s[j], j++;
if (curStr.length > maxStr.length && s.lastIndexOf(curStr) > i) maxStr = curStr;
}
}
return maxStr;
};
5、优化
评论区有同学表示Java
和C++
用这个思路会超时,可能有几个方面原因:
- 1、可能
Node.js
的下限比较低(受限于语言执行效率),所以Node.js
能过其他不行 - 2、字符串操作方法
replace
和+=
,Java
这里应该可以用StringBuilder
优化,C++
不太清楚能否优化 - 3、字符串索引方法
lastIndexOf
没有限制边界,最坏情况下会完整遍历原字符串,但是窗口扫过的字符串实际是不需要遍历的;改成下面这样限制查找起点能快700ms~1000ms左右
if (curStr.length > maxStr.length && s.indexOf(curStr, i + 1) > -1) maxStr = curStr;
还可以考虑手写索引方法,另外引入哈希表做缓存避免重复遍历;尝试了下,用例64(30000个v的字符串)超时,这个时候简单的缓存策略是完全失效的。
附上优化后的完整代码,只需修改 lastIndexOf
那一行
var longestDupSubstring = function (s) {
let maxStr = '', curStr = '';
for (let i = 0, j = 0; i < s.length; i++) {
curStr = curStr.replace(s[i - 1], '');
while (curStr.length <= maxStr.length && j < s.length) {
curStr += s[j], j++;
if (curStr.length > maxStr.length && s.indexOf(curStr, i + 1) > -1) maxStr = curStr;
}
}
return maxStr;
};
整体调整,更简洁的版本:
var longestDupSubstring = function (s) {
let maxStr = '';
for (let i = 0, j = 0; i < s.length; i++) {
while (j - i <= maxStr.length && j < s.length && ++j) {
if (j - i <= maxStr.length) continue;
const curStr = s.slice(i, j);
if (s.indexOf(curStr, i + 1) > -1) maxStr = curStr;
}
}
return maxStr;
};