感觉标题越来越长了haha
# 本章的内容是KMP算法、Manacher算法、滑动窗口、单调栈
# 本章的几个都是方法类的,熟练掌握很重要
# 字符串str1和str2,str1中是否包含str2,如果包含返回str2在str1开始的位置.
# 常规方法: 双指针,一个指向str1[0],一个指向str2[0],
# 如果两个指针指向的value相同,两个指针都+1,
# 如果不相同,str1指针+1,str2回到0位置
# 即以str1每一个字符作为开头,看看能不能配出完整的str2
def exist_conventional(str1, str2):
p1 = 0
p2 = 0
while p1 < len(str1):
pp1 = p1
while pp1 < len(str1) and str1[pp1] == str2[p2]:
pp1 += 1
p2 += 1
if p2 == len(str2):
return True
p1 += 1
p2 = 0
return False
# KMP算法: 如果每次遇到不匹配的字段就从str1下一字符和str2头开始匹配,过于浪费时间了,所以需要寻求一个加速的方法
# 找到str2中每一个字符前面的切片中,最长前缀和后缀相等的长度.
# 例如 "abc5647abc9" 中的9,对应的和后缀相等的最长前缀 (不能是完整的) 是"abc",返回的值就是len("abc")=3 (而不是abc5647abc)
# 怎么用? 假设在匹配的时候匹配到9的时候发现不对了,常规方法是str2回到0位置,时间复杂度为 O(N*M)
# 现在,我们算出了最长相等前缀和后缀的长度,因为我们是遍历到9的时候发现不匹配了,说明 9 前面的"abc"他是匹配的,
# 那么,我们自然也就可以不返回到str2的0位置和str1的next,而是从 index=3 开始继续查看,因为index=3的前面"abc"正是我们之前"9"对应的最长前后缀
def getSameStr(chr):
# 找到一串字符中的最长前缀和后缀,
# 两个特殊情况:1.如果在0位置就不匹配,应该str1跳转到next;2.如果在1位置不匹配应该str2返回到0位置
# 怎么计算?
# abbstabb ec abbstabb ? !
# 因为next值只考虑该位置前面的字符情况。所以?位置的next值是8,abbstabb=abbstabb。
# 求!位置的next值,首先拿?和?的前缀后一个位置相比,即"e"和?比较,如果一样则!位置的next值为8+1=9,abbstabbe=abbstabb?
# 如果不一样,则查看?的前缀后一个位置的next值,即"e"位置,"e"的next值为3,abb "s"=abb "e"=abb"s"=add "?"
# 比对"e"位置前缀的下一个位置"s"是否等于?,如果相等则next为4,abbs=abb?
# 如果不一样,则查看?的前缀后一个位置的前缀的后一个位置的next值,即"s"位置,"s"的next值为0
# 则比对0位置"a"和?,如果相等,则!的next值为1,否则为0
SameStrArray = []
for index in range(len(chr)):
if index == 0:
# 0位置的最长相同前后缀长度为-1
SameStrArray.append(-1)
continue
if index == 1:
# 0位置的最长相同前后缀长度为0
SameStrArray.append(0)
continue
# 初始最长前后缀为前一个字符的最长前后缀
lastindex = SameStrArray[index - 1]
while True:
# 如果 [index-1] == [前缀后一个]
if chr[index - 1] == chr[lastindex]:
# 当前index的最长前后缀长度+1
SameStrArray.append(lastindex + 1)
break
if lastindex > 0:
# 如果[index-1] != [前缀后一个],找前缀后一个的前缀
lastindex = SameStrArray[lastindex]
else:
# 如果0位置也不相同,就说明最长前后缀为0
SameStrArray.append(0)
break
return SameStrArray
def exist_KMP(str1, str2):
index = 0
p2 = 0
sameSTRarray = getSameStr(str2)
while index <= len(str1) - 1:
if str1[index] == str2[p2]:
index += 1
p2 += 1
else:
p2 = sameSTRarray[p2]
if p2 == -1:
index += 1
p2 = 0
if p2 == len(str2):
return True
return False
# 在字符串str中,找到最长的回文子串
# 常规解法在字符中间加上"#",查找以每一位字符为中心时的最长回文子串
def getLongPalindrome_conventional(chr):
chr = list(chr)
chr = "#" + "#".join(chr) + "#"
themax = -1
for index in range(len(chr)):
p1 = index - 1
p2 = index + 1
count = 1
while p1 >= 0 and p2 <= len(chr) - 1:
if chr[p1] == chr[p2]:
count += 2
else:
break
p1 -= 1
p2 += 1
themax = max(int(count / 2), themax)
return themax
# Manacher算法:
# R代表篇最右回文边界,C是最右回文边界的中心点
# 用i遍历str2,会出现四种情况:
# ......1a B ad e da B a2.......
# 假设现在r的范围是上面一块,index是第二个B,index2就是第一个B的位置,
# 情况2: 1、2也是r范围内,且index2范围是"aBa",因为 d=d、1=2、1!=d ,所以index范围也是"aBa",即半径和index2相同
# 情况3 1、2不是r范围内,且1=d,所以index2范围是"1aBad",因为 1!2、d=d、1=d,所以2!=d,所以index范围到"aBa",即半径是R到index
# 情况4 1、2不是r范围内,且1!=d,所以index2范围是"aBa",因为 1!2、d=d、1!=d,所以2?=d,所以index一直范围是"aBa",即已知半径是R到index,在此基础上继续往外扩
#
# 1.i在r的外面:从i开始求回文部分,这个过程会导致r和c都变化
# 2.i在r里面,但是i关于c的对称点的i'的回文区域在L..R以内:i的回文半径就是i'的回文半径,这个过程r和c都不变
# 3.i在r里面,但是i关于c的对称点的i'的回文区域有一部分在L...R以内:i的回文半径就是R到i的距离,这个过程r和c都不变
# 4.i在r里面,但是i关于c的对称点的i'的回文区域在L...R的边界上:i的回文半径就是i'的回文半径加上外扩的距离,如果外扩距离大于0则r和c都变,否则都不变
def getLongPalindrome_Manacher(chr):
chr = list(chr)
chr = "#" + "#".join(chr) + "#"
r = 0
C = -1
halfarr = []
for index in range(len(chr)):
if r > index:
halfarr.append(min(halfarr[2 * C - index], r - index))
else:
halfarr.append(1)
while index - halfarr[index] >= 0 and index + halfarr[index] <= len(chr) - 1:
if chr[index - halfarr[index]] == chr[index + halfarr[index]]:
halfarr[index] += 1
else:
break
if index + halfarr[index] > r:
r = index + halfarr[index]
C = index
return max(halfarr) - 1
'''
下面是很重要的一个方法----滑动窗口
'''
# 有一个arr和一个大小为w的窗口,输出每一种窗口下,窗口内的最大值
# 解题思路:窗口是先进先出顺序的,当一个很大的数进入窗口,怎么输出它呢?
# 设置一个辅助队列,保证后加入的较大的值永远保持在队列口位置,直到窗口内没有这个值
def maxWindowNum(arr, w):
queue = []
res = []
for i in range(len(arr)):
# i相当于是窗口的R,i-w相当于是窗口的L
while queue != [] and queue[len(queue) - 1] < arr[i]:
# 弹出又老又小的,插入新的,保证最大的排在最前
queue.pop()
queue.append(arr[i])
# 如果queue出口的数要被划走
if queue[0] == arr[i - w]:
queue.pop(0)
if i >= w - 1:
res.append(queue[0])
return res
# 最大子序列和(子序列和大于0)
def sum_subsequence(arr):
left = 0
themax = 0
sumdata = 0
ans = []
for right in range(len(arr)):
sumdata += arr[right]
if sumdata > themax:
# 如果当前子序列和是最大的,保存下来
themax = sumdata
ans = arr[left:right + 1]
if sumdata < 0:
# 如果子序列和小于0了,以为着这一段是拖后腿了,直接放弃,从下一个开始
sumdata = 0
left = right + 1
return ans, themax
'''
下面是个很重要的方法----单调栈
'''
# 单调栈
# 找到一个arr数组中每一位上,左右两边比它大的位置
# 常规方法:在每一位上左右遍历 O(N^2)
def boundary_conventional(arr):
res = []
for index in range(len(arr)):
for p1 in range(index - 1, -2, -1):
if p1 < 0:
p1 = None
break
if arr[p1] > arr[index]:
break
for p2 in range(index + 1, len(arr) + 1):
if p2 > len(arr) - 1:
p2 = None
break
if arr[p2] > arr[index]:
break
res.append([p1, p2])
return res
# 单调栈:构建一个栈底到栈顶单调递增的栈。
# 依次压入数,如果压入的数大于栈顶,开始弹出栈顶,直到能入栈。
# 弹出的数,下面是它左边比他大的数,要押入的数是比他右边比他大的数
def boundary_monotone(arr):
stack = []
res = []
for num in arr:
while stack != [] and stack[len(stack) - 1] < num:
stack.pop()
if stack == []:
res.append([None, num])
else:
res.append([stack[len(stack) - 1], num])
stack.append(num)
while stack != []:
stack.pop()
if stack == []:
res.append([None, None])
else:
res.append([stack[len(stack) - 1], None])
return res
# 如果arr中有重复值呢?
# 解题思路: 压入栈的时候压入一个数组,数组中顺序保存同value的index值
def boundary_monotone_SameValue(arr):
stack = []
res = []
for index in range(len(arr)):
while stack != [] and arr[stack[len(stack) - 1][0]] < arr[index]:
popindexs = stack.pop()
for i in popindexs:
if stack != []:
res.append([stack[len(stack) - 1][len(stack[len(stack) - 1]) - 1], index])
else:
res.append([None, index])
if stack != [] and arr[stack[len(stack) - 1][0]] == arr[index]:
stack[len(stack) - 1].append(index)
else:
stack.append([index])
while stack != []:
stack.pop()
if stack != []:
res.append([stack[len(stack) - 1], None])
else:
res.append([None, None])
return res
# 正数数组中,累计和与最小值的乘积,叫做指标A。给定一个数组,返回子数组中,最大指标A。
def getMAXindex_A(arr):
stack = []
res = 0
for index in range(len(arr)):
while stack != [] and arr[stack[len(stack) - 1][0]] > arr[index]:
popindex = stack.pop()
popnum = arr[popindex[0]]
for num in popindex:
sum_index_A = 0
if stack != []:
for i in range(stack[len(stack) - 1][len(stack[len(stack) - 1]) - 1] + 1, index):
sum_index_A += arr[i]
else:
for i in range(index):
sum_index_A += arr[i]
index_A = sum_index_A * popnum
res = max(res, index_A)
if stack != [] and arr[stack[len(stack) - 1][0]] == arr[index]:
stack[len(stack) - 1].append(index)
else:
stack.append([index])
while stack != []:
popindex = stack.pop()
popnum = arr[popindex[0]]
for num in popindex:
sum_index_A = 0
if stack != []:
for i in range(stack[len(stack) - 1][len(stack[len(stack) - 1]) - 1] + 1, len(arr)):
sum_index_A += arr[i]
else:
for i in range(len(arr)):
sum_index_A += arr[i]
index_A = sum_index_A * popnum
res = max(res, index_A)
return res