无重复字符的最长子串
- 我的思路:想要每次一遇到重复字符,就把与之重复的字符之前的全部切掉,然后在后面的字符串里继续寻找一个无重复子串,然后与前面找到的一个进行比较。最终,找出最长子串。然而,实际运行并不是我想象的那样,因为for循环中的i与length都已经不能被改变,我以为随着我对s的切片,在for循环中length就随之改变,且i也能重新开始轮回…(下面的程序还不正确)
def lesngthOfLongestSubstring(s: str) -> int:
s = list(s)
temp, result = [], []
length = len(s)
for i in range(length): # i, len(s)都无法再被改变了!
# print(s) # 验证了切片语句未起作用...
if s[i] not in temp:
temp.append(s[i])
else:
if len(temp) > len(result):
result = temp
temp.clear()
temp.append(s[i])
#s[s.index(s[i]):] # 这条语句不会起作用...
#length = len(s) # 这条语句不会起作用...
#i = 0 # 这条语句不会起作用...
return len(result)
print(lesngthOfLongestSubstring("pwwkew"))
-
一遍遍修改程序时,总能发现特殊情况没有考虑到(因为在运行时出现了与预期结果不一致的情况,所以才发现了特殊情况),然后一遍遍的进行bug的修补。
-
好吧,测试用例中总有我无法通过的,我还是放弃自己写吧…如下面的程序,当测试到"dvdf"时又出现了错误。。。(下面的程序还不正确)
def lesngthOfLongestSubstring(s: str) -> int:
if len(s) == 0:
return 0
if len(s) == 1:
return 1
s = list(s)
temp, result = [], []
length = len(s)
for i in range(length): # i, len(s)都无法再被改变了!
if s[i] not in temp:
temp.append(s[i])
if i == length - 1 and len(temp) > len(result):
result = temp
else:
if len(temp) > len(result):
result.clear()
for i in range(len(temp)):
result.append(temp[i])
#result = temp # 不能使用这种赋值方式,否则如果找到最长子串后,由改变了temp,那么result也就变了!如“cdd”,最后结果是1,而不是2了!
temp.clear()
temp.append(s[i])
return len(result)
print(lesngthOfLongestSubstring("dvdf"))
- 下面是参考其他人的答案进行的修正。
参考:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetcod/
暴力法
-
思路:逐个检查所有的子字符串,看它是否不含有重复的字符。一旦遇到重复的字符串,就检查下一子字符串。每计算出一个子字符串的长度后,就与前一个相比较,最终得到最大的无重复子字符串长度。
-
程序:
def lengthOfLongestSubstring(s):
'''
得到最长子字符串的长度
'''
n = len(s)
ans = 0
for i in range(n):
for j in range(i+1, n+1):
if allUnique(s, i, j):
ans = max(ans, j-i)
return ans
def allUnique(s, start, end):
'''
判断每个子字符串是否有重复的字符。如果有,则返回False,从而不必参与max(ans, j-i)比较
'''
set_ = set()
for i in range(start, end):
ch = s[i]
if ch in set_:
return False
set_.add(ch)
return True
print(lengthOfLongestSubstring("dvdf"))
- 运行过程:
对于测试字符串dvdf
来说,此程序将分别遍历子字符串d
,dv
,dvd
,dvdf
,v
,vd
,vdf
,d
,df
,f
,最终,得到最长子字符串长度为3
。- 运行结果:
- 算法思想:
- 为了枚举给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为
i
和j
。那么0 <= i <= j <= len(s)
。因此,使用i
从 0 0 0 到 n − 1 n - 1 n−1以及j
从 i + 1 +1 +1到 n n n这两个嵌套的循环,我们可以枚举出字符串s
的所有子字符串。- 要检查一个字符串是否有重复字符,我们可以使用集合。我们遍历字符串中的所有字符,并将它们逐个放入集合
set_
中。在放置一个字符之前,我们检查该集合是否已经包含它。如果包含,我们会返回False
。否则,我们就可以返回true
。
- 复杂度分析:
滑动窗口
说明:
- 暴力法非常简单,但它太慢了。
- 滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [ i , j ) [i, j) [i,j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [ i , j ) [i, j) [i,j)向右滑动1个元素,则它将变为 [ i + 1 , j + 1 ) [i+1, j+1) [i+1,j+1)(左闭,右开)。
思路:
- 我们使用列表类型的变量
l
存储当前窗口 [ i , j ) [i, j) [i,j)(最初 j = i j = i j=i)中;
注意:参考文章说的是使用HashSet类型的变量,因为他使用的是Java语言实现。而我使用Python语言实现,如果使用集合类型,在pop()操作时会出现不期望的结果,因为插入到集合中的元素的实际存储并不按照插入顺序。所以在这里我选择了集合类型变量。况且,Python中有
in
运算,实际上除此之外,选用元组这一有序序列也是可以的
- 然后我们向右侧滑动索引 j j j :
- 如果它不在列表
l
中,我们会继续滑动 j j j,直到 s[j] 已经存在于l
中。【此时,我们找到的没有重复字符的最长子字符串就是长度为 ( j + 1 − i ) (j+1-i) (j+1−i) 的以s[i]
开头、以s[j]
结尾的字符串。】 - 若s[j] 已经存在于
l
中,就对列表l
进行pop()
操作,同时改变 i i i,使 i i i向后滑动,即,令i += 1
。
- 循环步骤2,直至
i
i
i 或
j
j
j 不小于
len(s)
。
程序:
def lengthOfLongestSubstring(s):
n = len(s)
l = list()
ans, i, j = 0, 0, 0
while i < n and j < n:
#print(s[j])
if s[j] not in l:
l.append(s[j])
j += 1
#print("j", j)
ans = max(ans, j - i)
else:
l.pop(0)
#print("i", i)
i += 1
return ans
print(lengthOfLongestSubstring("dvdf"))
- 复杂度分析:
优化的滑动窗口
思路:
一旦遇到字符s[j]
与列表l
中的字符有重复时,我们不需要逐渐增加
i
i
i ,而是直接跳过 l[0] ~ 与s[j]相同的字符所在的位置
之间的所有字符,并将
i
i
i 变为 与s[j]相同字符所在的位置的索引 + 1
。
实现:
使用字典类型的变量,存储 不重复子字符串的每个字符及其在s
中的索引 之间的映射关系。一旦遇到重复字符,就可以通过字典中保存的映射关系找出这个重复字符在s
中的索引
x
x
x,然后
i
i
i 直接变为
(
x
+
1
)
(x+1)
(x+1) !
程序:
def lengthOfLongestSubstring(s):
n = len(s)
d = dict()
ans, i = 0, 0
for j in range(0, n):
if s[j] in d:
i = max(d.get(s[j])+1, i)
ans = max(ans, j+1-i)
d[s[j]] = j
return ans
print(lengthOfLongestSubstring("abcdc"))
- 复杂度分析: