LeetCode第3题:无重复字符的最长子串
题目描述:
要求:
第一种解法:遍历对比
这题我首先想到的就是对比,即将之前已经遍历过的元素加入列表中,利用python的in操作符,可以简单地进行对比,所以我们先建立一个store列表用来存储已遍历过的元素,用maxlen来记录最长字串长度,用tmplen来记录遍历过程字串长度的变化。这题用python能使代码更简洁,写起来也方便
store = []
maxlen = 0
tmplen = 0
武器有了,接下来开始实践,首先一个for循环取字符串s的字符
for i in s:
接下来开始if判断,如果当前的i元素已经存在于列表store,首先判断最长字串长度maxlen是否更新,如果临时记录字串长度的tmplen已经比maxlen长,则更新,相等和小于皆不更新
if i in store:
if maxlen < tmplen:
maxlen = tmplen
然后就是用index索引函数寻找当前在列表store中和i元素相同的下标,利用列表的切片功能将其后面的元素切出来赋予store(因为在store中和i相同的元素后面可能还有元素存在,所以需要用到切片),然后再将当前的i元素用append函数加入,再求一下当前列表长度即可
store = store[store.index(i)+1:]
store.append(i)
tmplen = len(store)
如果当前i元素不在列表store里面,则说明字串加上i元素后仍没有重复元素,此时tmplen需要加1,store列表再添加当前元素即可
else:
tmplen += 1
store.append(i)
最后还需要判断maxlen和tmplen之间的长度,因为上面的for循环在最后一个元素判断完后,若仍是新元素,则maxlen没有得到更新,所以最后要补一个更新,再返回maxlen即可完成
if maxlen < tmplen:
maxlen = tmplen
return maxlen
完整代码如下:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
store = []
maxlen = 0
tmplen = 0
for i in s:
if i in store:
if maxlen < tmplen:
maxlen = tmplen
store = store[store.index(i)+1:]
store.append(i)
tmplen = len(store)
else:
tmplen += 1
store.append(i)
if maxlen < tmplen:
maxlen = tmplen
return maxlen
复杂度分析
时间复杂度:这里我们只有一个for循环来对字符串进行遍历,for里面的语句在字符串长度为N时可以省略,因此时间复杂度为O(N)
空间复杂度:空间复杂度取决于我们所建立的store列表长度,最长的情况就是整个字符串都没有重复,因此空间复杂度也为O(N)?实际上我们的字符集是有长度限制的,以ascii码为例,一共就128个,因此空间复杂度最多也就O(128),官方用字母∣Σ∣表示,所以空间复杂度为O(∣Σ∣)
优缺
这个暂时还没想到。
第二种解法:滑动窗口
这种是LeetCode官方解法,代码更加简洁。解法用到了集合set()来记录,集合和字典一样,底层都是哈希表实现的,具体是将集合当作一个滑动可变长窗口,利用左i、右rk两个指针移动,当右指针移动时进行判断,若当前元素s[rk+1]不在集合里,就将其加入集合中,并且右指针rk+1,即右移一个元素,当元素在集合时,就会跳出while循环,并记录当前字串长度,然后进入下一个for循环,因为跳出for循环说明右指针指向的元素和左指针指向相同,因此就需要将左指针指向的元素删除
代码解析:
occ = set() #集合,充当滑动窗口
n = len(s) #for循环需要遍历的长度即是字符串s的长度
rk, ans = -1, 0 #rk是右指针(初始-1即还没开始移动,因为字符串初始下标为0),ans则是记录最长字串长度
for i in range(n):
if i != 0:
occ.remove(s[i - 1])
利用for循环的i来充当左指针的移动,当进行for循环且不为0是,必是因为出现相同的元素或者已经没有相同元素,拿到正确答案了
while rk + 1 < n and s[rk + 1] not in occ:
occ.add(s[rk + 1])
rk += 1
利用右指针右移,在每次右移过程中都会进行判断当前元素是否已经存在集合,没有添加当前元素,并右移(即rk+=1)
ans = max(ans, rk - i + 1)
这里就是ans最长字串长度的更新,当新的字串长度rk-i+1大时,就更新,没有就保持原值,最后return ans就行了
完整代码如下:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
occ = set()
n = len(s)
rk, ans = -1, 0
for i in range(n):
if i != 0:
occ.remove(s[i - 1])
while rk + 1 < n and s[rk + 1] not in occ:
occ.add(s[rk + 1])
rk += 1
ans = max(ans, rk - i + 1)
return ans
复杂度分析
时间复杂度:官方是说O(N),其中 N是字符串的长度。左指针和右指针分别会遍历整个字符串一次。因为最坏情况是O(∣Σ∣N),即当字符串长度为N,且是按照字符最大不重复长度的∣Σ∣循环组成的,首次遍历后,接下来每次for循环都要左右指针各移动一次,一共N次,当N无限大时,因为∣Σ∣是常数,因此省略,所以时间复杂度为O(N)
空间复杂度:为字符最大不重复的长度,即O(∣Σ∣)
优缺
运算结果相比解法1慢了一些,因此我觉得在字符串长度小的时候用第一种方法好一些,少了左指针的移动,因为解法1是直接切片,而不是一个一个移动的。在字符串很长时则解法1、2没有差别
运算时间可能会因本人电脑性能原因有差,这里仅供参考,具体以个人电脑运行为主
图均参考LeetCode
若有出错,请大家多批评指正!