Leetcode刷题(9) 双指针之滑动窗口系列(多用于字符串匹配)
76. 最小覆盖子串
方法: 滑动窗口, 双指针
class Solution(object):
def minWindow(self, s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
# 维护两个字典,其中都记录着字符和对应的频数
windows = dict() # 窗口内的need中包含的字符及其频数
need = dict() # 目标字符及其频数
for n in t:
need[n] = need.get(n, 0) + 1
valid = 0 # 已经ok的字符(窗口中该字符的次数>=need中对应字符的次数)
# 双指针(搜索区间左闭右开)
left = 0
right = 0
# 初始化最短长度
lenght = float('Inf')
nums = len(s)
# 记录最短的子串的开始和结束位置
start = 0
end = nums
# 外层right的循环; 内层left的循环
while(right < nums):
cur = s[right]
right += 1
if cur in need.keys(): # 加入的cur在need中
windows[cur] = windows.get(cur, 0) + 1
if windows[cur] == need[cur]:
valid += 1
while(valid == len(need)): # 满足→条件
# 先处理完任务之后, 再移动left
if lenght > float(right - left):
lenght = right - left
start = left
end = right
d = s[left]
left += 1
if d in need.keys(): # 删除的d在need中
if windows[d] == need[d]:
valid -= 1
windows[d] = windows.get(d, 0) - 1
return s[start: end] if lenght!=float('Inf') else ""
567. 字符串的排列
class Solution(object):
def checkInclusion(self, s1, s2):
"""
:type s1: str
:type s2: str
:rtype: bool
"""
need = dict()
for in_s1 in s1:
need[in_s1] = need.get(in_s1, 0) + 1
windows = dict()
valid = 0
s1_lenght = len(s1)
s2_lenght = len(s2)
left = 0
right = 0
while(right < s2_lenght):
r = s2[right]
right += 1
if r in need:
windows[r] = windows.get(r, 0) + 1
if windows[r] == need[r]:
valid += 1
# 排列的长度一定是等于p的长度,所以当windows的长度等于p_size就可以判断结果和开始收缩了
while(right - left >= s1_lenght):
if valid == len(need):
return True
l = s2[left]
left += 1
if l in need:
if windows[l] == need[l]:
valid -= 1
windows[l] = windows.get(l, 0) - 1
return False
class Solution(object):
def checkInclusion(self, s1, s2):
"""
:type s1: str
:type s2: str
:rtype: bool
"""
need = dict()
window = dict()
for c in s1:
need[c] = need.get(c, 0) + 1
left = 0
right = 0
vaild = 0
n = len(s2)
while(right < n):
r = s2[right]
right += 1
if r in need.keys():
window[r] = window.get(r, 0) + 1
if window[r] == need[r]:
vaild += 1
# 如果这里使用的是>而不是>=的话,结果判断需要放在当前循环结束后
while (right - left > len(s1)):
l = s2[left]
left += 1
if l in need.keys():
if need[l] == window[l]:
vaild -= 1
window[l] = window.get(l, 0) - 1
# 结果判断在这
if vaild == len(need):
return True
return False
438. 找到字符串中所有字母异位词
class Solution(object):
def findAnagrams(self, s, p):
"""
:type s: str
:type p: str
:rtype: List[int]
"""
windows = dict()
need = dict()
for n in p:
need[n] = need.get(n , 0) + 1
s_size = len(s)
p_size = len(p)
right = 0
left = 0
valid = 0
target_valid = len(need.keys())
res = list()
while(right < s_size):
# →移动right
r = s[right]
right += 1
# 判断r是否在need中, 并进行后续处理
if r in need.keys():
windows[r] = windows.get(r, 0) + 1
if windows[r] == need[r]:
valid += 1
# 排列的长度一定是等于p的长度,所以当windows的长度等于p_size就可以判断结果和开始收缩了
while(right-left >= p_size):
# 先判断结果
if valid == target_valid:
res.append(left)
# 再→移动left
l = s[left]
left += 1
# 判断l是否在need中, 并进行后续处理
if l in need.keys():
if windows[l] == need[l]:
valid -= 1
windows[l] = windows.get(l, 0) - 1
return res
3. 无重复字符的最长子串
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
right = 0
left = 0
res = 0
windows = dict()
while(right < len(s)):
r = s[right]
right += 1
windows[r] = windows.get(r, 0) + 1
while(windows[r] > 1):
l = s[left]
left += 1
windows[l] = windows.get(l, 0) - 1
# 结果的判断更新是在更新完left窗口之后,因为更新完窗口里面的字符才没有重复
res = max(right - left, res)
return res
下面是字符串的匹配问题, 我看和上面的也比较相似,所以也放这里来了
28. 实现 strStr()
暴力法:
# 暴力法
class Solution:
def strStr(self, haystack, needle):
L = len(needle)
n = len(haystack)
# 一定要保证从start开始后面有L个数(包括start上的元素)
for start in range(n - L + 1):
# 截取这段和needle进行比较
if haystack[start: start + L] == needle:
return start
return -1
KMP状态机法:
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
M = len(needle)
n = len(haystack)
if M == 0:
return 0
dp = [[0] * 256 for _ in range(M)]
# base case
dp[0][ord(needle[0])] = 1
def kmp():
# 影子状态初始化为0
X = 0
for j in range(1, M):
for c in range(256):
dp[j][c] = dp[X][c]
# 下标为j的字符被匹配了, 状态更新为j + 1
dp[j][ord(needle[j])] = j + 1
# 更新影子X的值
X = dp[X][ord(needle[j])]
def search():
j = 0
for i in range(n):
j = dp[j][ord(haystack[i])]
if j == M:
return i - M + 1
return -1
kmp()
return search()
10. 正则表达式匹配
递归法
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
if len(p) == 0:
return len(s) == 0
else:
# 不管其他情况,总是要先计算第一个字符的匹配情况
# 然后依据第一个字符的匹配情况来对接下来的情况进行处理
first = len(s) > 0 and p[0] in {'.', s[0]}
# 遇到通配符
if len(p) > 1 and p[1] == '*':
# 匹配0个 or 匹配1个
return self.isMatch(s, p[2:]) or first and self.isMatch(s[1:], p)
else:
# 正常匹配
return first and self.isMatch(s[1:], p[1:])
带备忘录的递归法
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
ns = len(s)
np = len(p)
mome = dict()
def dp(i, j):
if (i, j) in mome:
return mome[(i, j)]
# p没了
if j == np:
# s也应该没了
return i == ns
else:
# 不管其他情况, 总是要先计算第一个字符的匹配情况
# 然后依据第一个字符的匹配情况来对接下来的情况进行处理
# 首先保障s中还有未被匹配的字符
first = i < ns and p[j] in {'.', s[i]}
# 遇到通配符
if j < np - 1 and p[j + 1] == '*':
# 匹配0个 or 匹配1个
ans = dp(i, j + 2) or first and dp(i + 1, j)
# 正常匹配
else:
ans = first and dp(i + 1, j + 1)
mome[(i, j)] = ans
return ans
return dp(0, 0)
424. 替换后的最长重复字符
该种题型没有窗口收缩的操作,只有窗口的平移
class Solution(object):
def characterReplacement(self, s, k):
n = len(s)
right = 0
left = 0
maxlenght = 0
# 记录窗口中出现最大次数的字符的次数
maxCount = 0
# 记录了窗口中各种字符的频数
window = dict()
while right < n:
r = s[right]
window[r] = window.get(r, 0) + 1
# 更新当前窗口中有最大频数的字符
maxCount = max(maxCount, window[r])
# 右扩张
right += 1
# 更新结果
if maxCount + k >= right - left:
maxlenght = max(maxlenght, right - left)
# 左平移
else:
l = s[left]
window[l] -= 1
left += 1
return maxlenght
1004. 最大连续1的个数 III
相比上一题就是只增加了一个maxCount的更新条件,只在遇到1的时候更新
class Solution(object):
def longestOnes(self, A, K):
n = len(A)
right = 0
left = 0
maxlenght = 0
# 记录窗口中出现最大次数的字符的次数
maxCount = 0
# 记录了窗口中各种字符的频数
window = dict()
while right < n:
r = A[right]
window[r] = window.get(r, 0) + 1
# 更新当前窗口中有最大频数的字符
if r == 1: # 仅仅加了一个这个条件, 只在遇到1的情况下才更新maxCount
maxCount = max(maxCount, window[r])
# 右扩张
right += 1
# 更新结果, 然后不左缩,而是继续右扩寻找更优解
if maxCount + K >= right - left:
maxlenght = max(maxlenght, right - left)
# 左平移
else:
l = A[left]
window[l] -= 1
left += 1
return maxlenght
1234. 替换子串得到平衡字符串
解法是标准的滑动窗口,但是就是窗口的收缩条件比较难想
收缩条件是:两个指针间是待替换的子串,那么我们只需要保证在两指针区域外的字符的频率都小于n/4
class Solution(object):
def balancedString(self, s):
n = len(s)
minreplace = n
# count用来记录窗口外['Q', 'W', 'E', 'R']每个字符出现的频度
count = dict()
for c in ['Q', 'W', 'E', 'R']:
count[c] = 0
for c in s:
count[c] += 1
balance = n // 4
l = 0
r = 0
while r < n:
# 右扩来搜索满足条件的结果
right = s[r]
count[right] -= 1
r += 1
# 满足条件的时候左缩窗口取得最优解
while l < n and all(count[c] <= balance for c in ['Q', 'W', 'E', 'R']):
minreplace = min(minreplace, r - l)
left = s[l]
l += 1
count[left] += 1
return minreplace