问题
分析
单指针子字符串方法
这个问题第一眼就觉得,只要不断构建一个没有重复字符的子字符串就可以了。对输入的字符串逐个字符遍历,字符不在子字符串中就可以对输出的max
求一次值;字符串在子字符串中的话就对子字符串再截断一次。
s = 'abcabcbb' ↑ substr = 'a' max = 1 -------------------------------------------- s = 'abcabcbb' ↑ substr = 'ab' max = 2 -------------------------------------------- s = 'abcabcbb' ↑ substr = 'abc' max = 3 -------------------------------------------- s = 'abcabcbb' ↑ substr = 'abca' max = 3 -------------------------------------------- s = 'abcabcbb' ↑ substr = 'ab' -------------------------------------------- ...
二话不说就可以写出来,提交并通过:
let substr = '',
max = 0
for (const c of s) {
const idx = substr.indexOf(c)
substr += c
if (idx === -1)
max = Math.max(max, substr.length)
else
substr = substr.substring(idx + 1)
}
return max
设n
为输入的字符串的长度。这个方法的时间复杂度最佳的情况为max = 1
的时候:\(O(n)\),因为大循环只需要遍历一遍、内部小循环只需要遍历1次;最恶劣的情况为max = n
的时候:\(O(\frac{n^2}{2})\),因为内部小循环遍历的次数逐渐增大至n
。空间复杂度最佳的情况为\(O(n)\),因为要调用n
次substring
、每次子字符串占1个单位的空间;最恶劣的情况是\(O(\frac{n^2}{2})\),因为每次子字符串占的空间会逐渐增大至n
。
双指针免子字符串纯indexOf方法
稍微观察一下,其实就可以发现,子字符串的创建是多余的,因为它肯定会出现在输入的字符串中。只要使用两个指针代替原本的子字符串就可以免去多余的空间消耗:
↓ s = 'abcabcbb' ↑ max = 1 -------------------------------------------- ↓ s = 'abcabcbb' ↑ max = 2 -------------------------------------------- ↓ s = 'abcabcbb' ↑ max = 3 -------------------------------------------- ↓ s = 'abcabcbb' ↑ max = 3 -------------------------------------------- ↓ s = 'abcabcbb' ↑ -------------------------------------------- ...
有趣的是,这就需要有一个字符串的indexOf(char, start)
的变种方法:indexOf(char, start, end)/indexOf(char, start, count)
。Python与C#的字符串都有这个方法,但其他编程语言就没有了,但自己实现一个也不难。
这个方法的时间复杂度与前面的方法一致。空间复杂度就好很多了,只需要\(O(1)\)。代码就不展开了。
这里忍不住吐槽一下Swift和Rust的字符串!!Swift的String.Index
自成一格、压根不想你用Int
来索引字符;Rust的chars()
方法返回的是一个迭代器、不退化回C的unsafe
指针压根没有高效的索引方法!
双指针哈希表方法
外部的大循环\(O(n)\)肯定是没办法优化的,但内部的小循环\(O(1) \text{~} O(n)\)感觉上是可以使用哈希表来优化掉变成\(O(1)\)的,这样总体的时间复杂度就可以控制在\(O(n)\),而空间复杂度则会增加到\(O(n)\)。很容易就可以将前面的方法内的indexOf
方法替换为哈希表查找;为了避免从哈希表中删除元素(因为慢),需要在哈希表中找到字符对应的下标索引之后还要与start
指针比较,这样才算完整的查找重复方法。
但实际上提交之后发现这个方法运行起来并没有比前面的快多少,甚至部分编程语言用这个方法还比前面的慢。😅
值得一提的是,在C/C++的环境下,可以使用int map[256]
来替换哈希表。瞄了一眼别人提交的代码发现了这个技巧,因为测试案例里面都只有ASCII码的字符串,而ASCII码只有0 ~ 255
256个字符。
答案
打赏作者