题目
思路
哈希表记录是否有重复,驱动双指针滑动窗口
代码
class Solution {
func lengthOfLongestSubstring(s: String): Int64 {
let len = s.size. // 输入长度
if(len < 2){ // 特殊情况
return len
}
var r = 1 // 右指针的初始位置
var res = 1 // 记录最长子串的长度
var duplicate:Int8 = -1 // 记录重复字符
var hash = HashSet<UInt8>()
for(left in 0..len){
// println("left ${left} duplicate ${duplicate} s[left] ${s[left]}")
if(duplicate >= 0){ // 存在重复字符
hash.remove(s[left-1]) // 删除上个左字符
// println("remove ${s[left-1]} because duplicate ${duplicate}")
if(s[left-1] == UInt8(duplicate)){ // 如果去重成功,则初始化duplicate
duplicate = -1
r = max(r, left +1) // 更新右指针,防止相同字符连续出现导致连续消除
}
// println("duplicate ${duplicate}")
}
hash.put(s[left]) // 左字符入hash
// print("myset ")
// for (i in hash) {
// print("${i} ")
// }
// println("")
for(right in r..len){
r = right
// println("s[right] ${s[right]}")
// println("hash.contains(s[right] = ${hash.contains(s[right])}")
if(hash.contains(s[right])){ // 发现重复字符,记录
duplicate = Int8(s[right])
break
}
hash.put(s[right]) // 右字符入hash
res = max(res, hash.size) // 更新答案
// println("res = ${res}")
if(right == len - 1){ // 遍历到底,直接返回
return res
}
}
}
return res
}
}
复杂度
平均情况
时间复杂度
-
遍历字符串:外层循环是通过左指针遍历整个字符串,时间复杂度为
O(n)
,其中n
是字符串的长度。 -
内层循环:内层循环是通过右指针从当前右边界
r
向右遍历,每个位置最多只会被访问一次,因此在整个执行过程中,所有的字符总共也只会被访问一次,因此内层循环的总体时间复杂度也是O(n)
。 -
哈希集合的操作:插入、删除和查找操作在哈希集合中平均时间复杂度为 (O(1))。
结合以上分析,外层和内层循环虽然各自是线性的,但在平均情况下没有重复的情况下,整体复杂度仍然保持在 O(n)
。
因此,时间复杂度是 O(n)
。
空间复杂度
- 哈希集合:根据输入字符串的不同字符,哈希集合会使用额外的空间来存储字符。在最坏情况下,当字符串包含
m
个不同字符时,空间复杂度为O(m)
,其中m
是字符集的大小。
因此,空间复杂度是 O(m)
,在 ASCII 字符集上通常为常数复杂度 O(1)
,但是如果考虑 Unicode 字符集可能会更大。
最坏情况
最坏情况的时间复杂度
-
字符串特征: 在最坏情况下,输入字符串可能全部由相同的字符组成,比如
"aaaaaa"
。这意味着无论左指针或右指针如何移动,总会发现重复字符。 -
外层循环: 外层循环会遍历整个字符串,一共进行
n
次迭代,其中n
是字符串的长度。 -
内层循环: 在最坏情况下,右指针从当前位置
r
到字符串末尾的每次推进都会遭遇重复,导致内层循环结束后重新进入外层循环。因此,右指针可能需要在每次外层循环中几乎遍历整条链(即从当前左右指针的相对位置到末尾)。
因此,内层循环的复杂度在最坏情况下也可能接近 O(n)
。
在这个特殊情况下,即每个字符都是重复的,内外层循环可能会导致重复的遍历,因此最坏的时间复杂度可表示为 O(n^2)
。
最坏情况的空间复杂度
和平均一样
遇到的坑
1、家人们谁懂啊,仓颉的s[i]居然是UInt8,string的分量居然不是rune(char)