python算法复习(七)----KMP、Manacher、滑动窗口和单调栈

感觉标题越来越长了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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值