
本文旨在深入解析求解字符串中最长无重复子串长度的滑动窗口算法。我们将分析一种常见的实现方式,指出其潜在的时间复杂度问题,并提供一种更优的、时间复杂度为 O(n) 的解决方案。通过代码示例和详细解释,帮助读者理解算法原理并掌握优化技巧。
给定一个字符串,找出其中最长且不包含重复字符的子串的长度。例如:
最初的解决方案采用滑动窗口的思想,使用一个对象 (storage.cache) 来缓存字符及其索引。虽然看起来像是滑动窗口,但由于在遇到重复字符时,存在一个内部循环,导致其时间复杂度并非严格的 O(n)。
以下是原始代码:
var lengthOfLongestSubstring = function(str) {
// Create storage object for caching
let storage = {
longestSubStringLength: 0,
longestSubString: 0,
cache: {
subString: ''
}
};
// Loop through string
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (!storage.cache[char] && storage.cache[char] !== 0) {
// If current letter is not in storage, add it and extend current substring
storage.cache[char] = i;
storage.cache.subString += char;
} else {
// If current letter is already in storage, start a new round
let previousCache = storage.cache;
storage.cache = {
subString: ''
};
if (previousCache[char] + 1 !== i) { // If there are letters in-between
storage.cache.subString = str.substring(previousCache[char] + 1, i);
for (let j = previousCache[char]; j < i; j++) {
storage.cache[str[j]] = j;
}
}
storage.cache[char] = i;
storage.cache.subString += char;
}
// If current substring is the longest, update it in storage
if (storage.cache.subString.length > storage.longestSubStringLength) {
storage.longestSubStringLength = storage.cache.subString.length;
storage.longestSubString = storage.cache.subString;
}
}
return storage.longestSubStringLength;
};问题在于 else 分支中的内部 for 循环:
for (let j = previousCache[char]; j < i; j++) {
storage.cache[str[j]] = j;
}这个循环在遇到重复字符时,会迭代更新 storage.cache 中位于重复字符之间的字符的索引。在最坏情况下,例如 "abcdefghabcdefgh",这个内部循环可能会执行多次,导致整体时间复杂度高于 O(n)。 更准确地说,其时间复杂度接近 O(n*m),其中 m 是最长不重复子串的平均长度。
为了实现真正的 O(n) 时间复杂度,我们可以使用 Map 数据结构来存储字符及其索引。Map 提供了快速的查找和更新操作。
以下是优化后的代码:
const lengthOfLongestSubstring = str => {
let cnt = 0;
let n = str.length;
let answer = 0;
let map = new Map(); // to store the strings and their length
for (let start = 0, end = 0; end < n; end++) { // slide
// move start if the character is already in the map
if (map.has(str[end])) start = Math.max(map.get(str[end]), start);
answer = Math.max(answer, end - start + 1); // longest string
map.set(str[end], end + 1);
cnt++
}
return [str, `lookups: ${cnt} lookups:`, "answer", answer];
}
["abcabcbb", "bbbbb", "pwwkew", "abcdefghabcdefgh"].forEach(str => console.log(lengthOfLongestSubstring(str).join(" ")))代码解释:
时间复杂度分析:
因此,整体时间复杂度为 O(n)。
空间复杂度分析:
空间复杂度为 O(min(m, n)),其中 m 是字符集的大小,n 是字符串的长度。这是因为 Map 最多存储 m 个不同的字符及其索引。
通过使用 Map 数据结构和滑动窗口技术,我们可以高效地解决最长无重复子串问题,并将时间复杂度优化到 O(n)。 关键在于正确地维护滑动窗口的起始位置,并利用 Map 快速查找和更新字符的索引。 这种方法不仅提高了效率,还使代码更简洁易懂。
以上就是求解最长无重复子串长度:滑动窗口算法的时间复杂度分析与优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号