最长不含重复字符的字符串
offer43的给出了一个字符串,要求其中最长不含重复字符的子字符串。
本例题目描述简单,但是方法多种多样,比较容易想到的是暴力方法和滑动窗口。就python而言,我们可以利用字典中不能有重复元素的特性来解决。此外,也能通过动态规划来解决。
暴力遍历
暴力遍历要求两次遍历,复杂度是 O ( n 2 ) O(n^2) O(n2),反正我是能接受的,天河二号也是能接受的。
# offer43-solution 1
def lengthOfLongestSubstring(self, s: str) -> int:
max_count = 0 # 最长不重复子字符串的长度
record = set() # 记录当前子串中以有哪些字符
i, j = 0, 0 # 记录子串的起点和终点
while i <= j and j < len(s):
if s[j] not in record: # 如果子串没重复,长度+1
record.add(s[j])
j += 1
else: # 如果子串重复了,重置record
i = j = i+1
record.clear() # 清空
if len(record) > max_count:
max_count = len(record)
return max_count
滑动窗口法
“滑动窗口”这个概念在计算机算法中非常常见。该算法可以把嵌套的循环转化为单循环从而降低时间复杂度,比较常见的领域的话,TCP协议的滑动窗口进行流量控制,或图像处理中的物体识别都会用到。
其原理大致分成如下几步:
1、初始化头尾指针 head,tail;
2、tail 右移,判断 tail 指向的元素是否在 [head:tail] 的窗口内;
3、如果窗口中没有该元素,则将该元素加入窗口,同时更新窗口长度最大值,tail 右移;否则将 head 指针右移,直到窗口中不包含该元素。
4、返回窗口长度的最大值。
代码不是我写的,见参考链接3,我一开始就没往滑动窗口的方向想。
# offer43-solution 2
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) <= 1:
return len(s)
head, tail = 0, 0
maxLen = 1
while tail+1 < len(s):
tail += 1 # 往窗口内添加元素
if s[tail] not in s[head: tail]: # 窗口内没有重复的字符,实时更新窗口最大长度
maxLen = max(maxLen, tail - head + 1)
else: # 窗口内有重复字符,移动head直至窗口内不再含重复的元素
while s[tail] in s[head: tail]:
head += 1
return maxLen
字典法
利用字典的不重复性,只需要一个start指针就可以完成了,这也是我第一想到的方法。python不仅仅语法糖多,好使的玩意儿也不少。
# offer43-solution 3
class Solution:
def __init__(self):
self.maxString = []
def longestSubString(self, inputString):
dic = {}
dic = dic.fromkeys(inputString, 0)
self.maxString.append(inputString[0])
for i in range(len(inputString)):
for j in range(i, len(inputString)):
if dic[inputString[j]] != 0:
dic = dic.fromkeys(inputString, 0)
break
else:
if j - i + 1 > len(self.maxString[0]):
self.maxString = []
self.maxString.append(inputString[i:j+1])
elif j - i + 1 == len(self.maxString[0]):
self.maxString.append(inputString[i:j+1])
dic[inputString[j]] += 1
sol = Solution()
sol.longestSubString(inputString)
动态规划
虽然我一开始没想到DP,但是半抄半搬地写了个字典(见参考链接4)出来以后,我突然觉得整个DP最头大的部分可以用字典解决,然后就奔着DP去思考了。
定义函数
f
(
i
)
f(i)
f(i)为以第
i
i
i个字符为结尾的不包含重复字符的子字符串的最长长度,第i个字符和它上次出现在字符串中的位置的距离为
d
d
d
如果第
i
i
i个字以前没有出现过,那么
f
(
i
)
=
f
(
i
−
1
)
+
1
f(i) = f(i-1)+1
f(i)=f(i−1)+1,否则分两种情况:
1、
d
≤
f
(
i
−
1
)
d≤f(i-1)
d≤f(i−1),此时第
i
i
i个字符上次出现在
f
(
i
−
1
)
f(i-1)
f(i−1)对应的最长子字符串之中即
f
(
i
)
=
d
f(i) = d
f(i)=d
2、
d
>
f
(
i
−
1
)
d>f(i-1)
d>f(i−1),此时第
i
i
i个字符上次出现在
f
(
i
−
1
)
f(i-1)
f(i−1)对应的最长子字符串之前,即
f
(
i
)
=
f
(
i
−
1
)
+
1
f(i) = f(i-1) + 1
f(i)=f(i−1)+1
最头大的部分就是计算这个
d
d
d,但有了字典就好说了。
# offer43-solution 4
class Solution:
def lengthOfLongestSubstring(self, s):
start = 0
maxLength = 0
usedChar = {}
for i in range(len(s)):
if s[i] in usedChar and start <= usedChar[s[i]]:
start = usedChar[s[i]] + 1
else:
maxLength = max(maxLength, i - start + 1)
usedChar[s[i]] = i
return maxLength
第一个只出现一次的字符
offer45要求:在一个字符串( 1 < = l e n g t h < = 10000 1<=length<=10000 1<=length<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。
字典
毫无悬念字典冲冲冲,简单又快捷。
# offer45-solution 1
def FirstNotRepeatingChar(self, s):
usedChar = {}
for i in range(len(s)):
if s[i] in usedChar:
usedChar[s[i]] = -1
else:
usedChar[s[i]] = i
p = usedChar.values()
p = [i for i in p if i >= 0]
return min(p)
非字典
我当初是写完就了事了,后来做笔记总结的时候,也看到了非字典的处理方法。首先需要明确,无论是否使用字典,都必须遍历字符串,统计每个字符出现的次数,然后再遍历一遍字符串,如果某个字符出现的次数是1,则返回这个字符的索引。
在不使用字典的情况下,参考链接5给出了换一个数据类型来存储字符串出现的次数的思路,我照搬代码,共同学习。
# offer45-solution 2
def FirstNotRepeatingChar(string):
# 记录每个字符出现的次数
lst = [0 for i in range(26)]
for item in string:
lst[ord(item) - 97] +=1
for index, item in enumerate(string):
if lst[ord(item) - 97] == 1:
return index
return -1
参考
滑动窗口算法基本原理与实践
Leetcode刷题总结之滑动窗口法(尺取法)
最长不含重复字符的子字符串(Python and C++解法)
最长不包含重复字符的子字符串【python】
第一个只出现一次的字符
Python字典及基本操作(超级详细)