字符串反转
LeetCode541题 反转字符串II
模拟反转过程就可以
class Solution:
def reverseStr(self, s: str, k: int) -> str:
def reverse_substring(text):
left, right = 0, len(text) - 1
while left < right:
text[left], text[right] = text[right], text[left]
left += 1
right -= 1
return text
res = list(s)
for cur in range(0, len(s), 2 * k):
res[cur: cur + k] = reverse_substring(res[cur: cur + k])
return ''.join(res)
LeetCode151题 反转字符串中的单词
先反转一遍字符串,再反转一遍字符串中的单词,就是要求的结果
class Solution:
def reverseWords(self, s: str) -> str:
res = ''
s = s[:: -1]
left = 0
right = 0
while right < len(s):
if s[right] == ' ' and right > 0 and s[right - 1] != ' ':
res += ' ' + s[left: right][:: -1]
left = right + 1
elif s[right] == ' ':
left = right + 1
elif right == len(s) - 1:
res += ' ' + s[left: right + 1][:: -1]
right += 1
return res[1:]
双指针法
LeetCode5题 最长回文子串
回文串的的长度可能是奇数也可能是偶数,解决该问题的核心是从中心向两端扩散的双指针技巧。如果输入相同的左右指针,就相当于寻找长度为奇数的回文串,如果输入相邻的左右指针,则相当于寻找长度为偶数的回文串
class Solution:
def longestPalindrome(self, s: str) -> str:
res = ''
for i in range(len(s)):
s1 = self.longest_huiwen(s, i, i)
s2 = self.longest_huiwen(s, i, i + 1)
res = s1 if len(s1) > len(res) else res
res = s2 if len(s2) > len(res) else res
return res
def longest_huiwen(self, s, l, r):
# 中间向两边扩散
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1
r += 1
return s[l + 1: r]
LeetCode844题 比较含退格的字符串
一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
cur_s = len(s) - 1
cur_t = len(t) - 1
d_num_s = d_num_t = 0
while cur_s >= 0 or cur_t >= 0:
while cur_s >= 0 and (s[cur_s] == '#' or d_num_s > 0):
if s[cur_s] == '#':
d_num_s += 1
else:
d_num_s -= 1
cur_s -= 1
while cur_t >= 0 and (t[cur_t] == '#' or d_num_t > 0):
if t[cur_t] == '#':
d_num_t += 1
else:
d_num_t -= 1
cur_t -= 1
if cur_s >= 0 and cur_t >= 0 and s[cur_s] == t[cur_t]:
cur_s -= 1
cur_t -= 1
elif cur_s < 0 and cur_t < 0:
return True
else:
return False
return True
滑动窗口
滑动窗口算法技巧主要用来解决子数组问题,比如让你寻找符合某个条件的最长/最短子数组或者子串。对于某些题目,并不需要穷举所有子串,就能找到题目想要的答案。滑动窗口就是这种场景下的一套算法模板,帮你对穷举过程进行剪枝优化,将求解子串复杂度由O(N^2)->O(N)
LeetCode3题 无重复字符的最长子串
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
res = 0
left = 0
right = 0
visited = dict()
while right < len(s):
visited[s[right]] = visited.get(s[right], 0) + 1
# 当新增元素数量>1时,需要持续向右移动left指针
while visited[s[right]] > 1:
visited[s[left]] -= 1
left += 1
# 统计加入s[right]时满足条件的子串长度
res = max(res, right - left + 1)
right += 1
return res
LeetCode76题 最小覆盖子串
class Solution:
def minWindow(self, s: str, t: str) -> str:
res = ''
# valid记录被覆盖的字符数
valid = left = right = 0
# need统计t中字符数量,window记录窗口中字符数量
need, window = dict(), dict()
for i in t:
need[i] = need.get(i, 0) + 1
while right < len(s):
window[s[right]] = window.get(s[right], 0) + 1
# s[right]被覆盖后,valid+1
if s[right] in need and window[s[right]] == need[s[right]]:
valid += 1
# 如果t被完全覆盖,先更新结果,再缩小窗口
while valid == len(need):
res = s[left: right + 1] if right - left + 1 < len(res) or res == '' else res
# 缩小窗口过程中,若出现字符串不能被覆盖,valid-1
if s[left] in need and window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
right += 1
return res
LeetCode438题 找到字符串中所有字母异位词
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
res = []
valid = left = right = 0
need, window = dict(), dict()
for i in p:
need[i] = need.get(i, 0) + 1
while right < len(s):
if s[right] not in need:
while left < right:
if window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
left += 1
else:
window[s[right]] = window.get(s[right], 0) + 1
if window[s[right]] == need[s[right]]:
valid += 1
while window[s[right]] > need[s[right]]:
if window[s[left]] == need[s[left]]:
valid -= 1
window[s[left]] -= 1
left += 1
if valid == len(need):
res.append(left)
right += 1
return res
KMP算法
KMP算法可以使字符串匹配算法用线性复杂度实现。
一个字符串的前缀指包含首字母、不包含尾字母的所有子串,后缀指包含尾字母、不包含首字母的所有子串。KMP算法的关键是求模式串的每个子串的最长相等前后缀的长度,即next数组。
利用next数组,文本串的指针不需要回退,就可以实现字符串的匹配。当文本串[i]不匹配模式串[j]时,j跳到next[j-1]的位置继续匹配,就可以进行字符串匹配。
求next数组步骤:
- 初始化,前缀末尾位置j,后缀末尾位置i
- 前后缀末尾不同情况,不断向前移动前缀末尾位置,j=next[j-1]
- 前后缀末尾相同情况,向后移动前缀末尾位置,j++
- 更新后缀末尾位置i对应的next[i]=j
LeetCode28题 找出字符串中第一个匹配的下标
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
next_list = self.getNext(needle)
j = 0
for i in range(len(haystack)):
while j > 0 and haystack[i] != needle[j]:
j = next_list[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1
def getNext(self, s):
next_list = [0] * len(s)
j = 0
for i in range(1, len(s)):
while j > 0 and s[j] != s[i]:
j = next_list[j - 1]
if s[j] == s[i]:
j += 1
next_list[i] = j
return next_list
LeetCode459题 重复的子字符串
移动匹配法
令t=s[1:] + s[:-1],若t包含s,则说s由重复子字符串组成
证明:令t包含s的起点索引为i,则s=t[i:n+i]=s[i:n] + s[0:i],这说明如果t包含s,则s是一个可旋转的字符串,即将s的前i个字符保持顺序,移动到s的末尾,得到的新字符串与s相同。由此我们可以推导出对任意的未越界的索引j,s[j]=s[j+i]=s[i+2*i]=...=s[j+k*i],说明s是由重复的字符串组成的
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
t = s[1:] + s[:-1]
next_list = self.getNext(s)
j = 0
for i in range(len(t)):
while j > 0 and s[j] != t[i]:
j = next_list[j - 1]
if s[j] == t[i]:
j += 1
if j == len(s):
return True
return False
def getNext(self, s):
next_list = [0] * len(s)
j = 0
for i in range(1, len(s)):
while j > 0 and s[j] != s[i]:
j = next_list[j - 1]
if s[j] == s[i]:
j += 1
next_list[i] = j
return next_list
KMP解法(利用最长相等前后缀的性质)
如果字符串s的最长相等前后缀剩下的部分的长度可以被len(s)整除,则说明s是由重复的字符串组成的
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
t = s[1:] + s[:-1]
next_list = self.getNext(s)
j = 0
if next_list[-1] != 0 and len(s) % (len(s) - next_list[-1]) == 0:
return True
return False
def getNext(self, s):
next_list = [0] * len(s)
j = 0
for i in range(1, len(s)):
while j > 0 and s[j] != s[i]:
j = next_list[j - 1]
if s[j] == s[i]:
j += 1
next_list[i] = j
return next_list