无重复字符的最长子串 双指针解法
题目描述
给定一个字符串 s
,请你找出其中不含有重复字符的最长子串的长度。LeetCode对应题目
解题思路
做这道题的时候第一个想到的是动态规划,因为与寻找最长回文子序列有些相似,想着用f[i][j]
来表示下标i
到下标j
子串的状态。但在做的过程当中,发现必须存储当前已经遍历过了哪些字符,而用f[i][j]
存储子状态遍历过哪些字符好像在空间上面会开辟大量的空间,于是选择另寻他路。
最容易的想法是暴力求解,用两个指针i
和j
。依次增加i
的值,每次增加i
的值时j
从i+1
遍历到字符串的结尾,这样的时间复杂度是
O
(
N
2
)
O(N^2)
O(N2),对于长一些的测试用例多半是通不过的,于是想办法进行优化,想想哪些地方可以直接跳过。
分析后发现三点技巧:
- 如果 s i ⋯ s j s_i\cdots s_j si⋯sj没有重复,而 s j + 1 s_{j+1} sj+1与其中的某一个重复了,那么后续的字符就不用再遍历,直接进行下一轮迭代
- 假设
s
j
+
1
s_{j+1}
sj+1是与
s
i
⋯
s
j
s_i\cdots s_j
si⋯sj中的
s
k
(
i
<
k
<
j
)
s_k(i<k< j)
sk(i<k<j)重复了,如果我们下一轮迭代从
s
i
+
1
s_{i+1}
si+1开始迭代,那么必然又会遇到
s
j
+
1
s_{j+1}
sj+1与
s
k
s_k
sk重复的情况。又因为我们是要求最大的无重复子串长度,所以这必然会比原来从
s
i
s_i
si开始的子串更短,因此这样的迭代是没有意义的,我们可以直接将
i
跳到k+1
来防止这样的无用迭代 - 在发现重复字符之前的字符都是不重复的,如果我们每次都将
j
退回到i+1
那么就会将以前已经知道的不重复的字符又重新判断一次,这也无疑是无用功。因此,我们根本就没有必要将j
往回退,当i
的值更新后j
继续进行判断即可
经过这三点技巧,可以发现i
和j
都是一直在往前走的,也就是说当j==len(s)
的时候整个算法就结束了,整个算法的时间复杂度降到了
O
(
N
)
O(N)
O(N)
实现代码
def lengthOfLongestSubstring(s):
n = len(s)#获取字符串长度
if not n:
return 0
max_len = 1#至少长度为一,因为单个字符是无重复的
i,j=0,1
while j<n:
index = s[i:j].find(s[j])#找到该字符在已检测子串中的位置
if index == -1:#表示没有重复出现过
if j - i + 1 > max_len:
max_len = j - i + 1
j+=1
else:#重复出现
i += index+1
return max_len