数据结构与算法--python学习笔记五(听了左神的课,自己整理的,左神还是强啊)

def getValueFromStr(str1):
    """给定字符串,字符串表示一个公式,可能有整数、加减乘除符号和左右括号,返回公式计算结果"""
    return valueProcess(str1, 0)[0]


# 返回两个值:计算结果,计算到的位置
def valueProcess(str1, index):
    pre = 0
    que = []
    while index < len(str1) and str1[index] != ')':
        if '0' <= str1[index] <= '9':
            pre = pre * 10 + int(str1[index])
            index += 1
        elif str1[index] != "(":  # str1[index] 此时是 +-*/
            addNum(que, pre)
            que.append(str1[index])
            index += 1
            pre = 0
        else:  # str1[index] 此时为 '('
            bra = valueProcess(str1, index + 1)
            pre = bra[0]
            index = bra[1] + 1
    addNum(que, pre)
    return getResult(que), index


def addNum(que, num):
    if que != []:
        topStr = que.pop()
        if topStr in '+-':
            que.append(topStr)
        else:
            cur = int(que.pop())
            num = cur * num if topStr == '*' else cur // num
    que.append(str(num))


def getResult(que):
    res = 0
    addFlag = True
    while que != []:
        cur = que.pop(0)
        if cur == '+':
            addFlag = True
        elif cur == '-':
            addFlag = False
        else:
            res += int(cur) if addFlag else -int(cur)
    return res


class SkipListNode(object):
    """跳表节点"""
    def __init__(self, value):
        self.value = value
        # 列表中存储的是节点,nextNodes[1]指的是这一层所指向的下一个节点是什么
        self.nextNodes = []


class SkipList(object):
    def __init__(self):
        self.size = 0  # 加入了多少个key
        self.maxLevel = 0  # 所有数据随机出的最大层
        self.head = SkipListNode(None)  # 巨小,整个结构最前面的链,层数与所有数据随机出的最大层一样
        self.head.nextNodes.append(None)
        self.PROBABILITY = 0.5

    def add(self, newValue):
        if not self.contains(newValue):
            self.size += 1
            level = 0
            while random.random() < self.PROBABILITY:
                level += 1
            while level > self.maxLevel:
                self.head.nextNodes.insert(0, None)
                self.maxLevel += 1
            newNode = SkipListNode(newValue)
            current = self.head
            while level >= 0:
                current = self.findNext(newValue, current, level)
                newNode.nextNodes.insert(0, current.nextNodes[level])
                current.nextNodes[level] = newNode
                level -= 1

    def findNext(self, e, current, level):
        nextNode = current.nextNodes[level]
        while nextNode is not None:
            value = nextNode.value
            if self.lessThan(e, value):
                break
            current = nextNode
            nextNode = current.nextNodes[level]
        return current

    def contains(self, value):
        node = self.findNode(value)
        return node is not None and node.value is not None and node.value == value

    def findNode(self, e):
        return self.find(e, self.head, self.maxLevel)

    def lessThan(self, a, b):
        return a < b

    def find(self, e, current, level):
        while level >= 0:
            current = self.findNext(e, current, level)
            level -= 1
        return current

    def delete(self, deleteValue):
        if self.contains(deleteValue):
            deleteNode = self.findNode(deleteValue)
            self.size -= 1
            level = self.maxLevel
            current = self.head
            while level >= 0:
                current = self.findNext(deleteNode.value, current, level)
                if deleteNode.nextNodes.size > level:
                    current.nextNodes[level] = deleteNode.nextNodes[level]
                level -= 1


def getMaxEor1(alist):
    maxNum, eor = 0, 0
    dp = [0] * len(alist)
    for i in range(len(alist)):
        eor ^= alist[i]
        maxNum = max(maxNum, eor)
        for start in range(1, i + 1):
            curEor = eor ^ dp[start - 1]  # eor(alist[start:i]) = eor(alist[:i]) ^ eor(alist[:start-1])
            maxNum = max(maxNum, curEor)
        dp[i] = eor
    return maxNum


class NumTrieNode(object):
    def __init__(self):
        self.nexts = [None] * 2


class NumTrie(object):
    def __init__(self):
        self.head = NumTrieNode()

    def add(self, num):
        curNode = self.head
        for move in range(31, -1, -1):
            path = (num >> move) & 1  # 从高位依次取数
            curNode.nexts[path] = NumTrieNode() if curNode.nexts[path] is None else curNode.nexts[path]
            curNode = curNode.nexts[path]

    # 为了求以i为结尾最大的异或和,假设从start开始到i结束的异或和是最大的。
    # 由公式eor(alist[start:i]) = eor(alist[:i]) ^ eor(alist[:start-1]),但由于k无法得知,
    # 而eor(alist[:i])是从头开始异或,是已知的,因此可以按照已知的eor(alist[:i])推测当eor(alist[:start-1])是什么时,
    # 可以使eor(alist[start:i])成为最大的。而eor(alist[start:i])最好的结果是符号位是0,其他位均为1。
    # 即使最高位被迫只能为1(因为eor(alist[:start-1])全都是以1开始的)时,其他位也最好能为1,此时是最大的(负数是以补码的形式存在的)
    def maxXor(self, num):
        curNode = self.head
        res = 0
        for move in range(31, -1, -1):
            path = (num >> move) & 1  # 从高位一次取数
            # 为了使数最大,最高位符号位最好的结果是0(正数),其他位最好的结果是1。
            best = path if move == 31 else path ^ 1  # 期待要选的路
            # 如果有理想的数字通道,当然按照理想的走,但如果实际并没有,只能按实际来,下一位在试,这样是最大的
            best = best if curNode.nexts[best] is not None else best ^ 1  # 实际选择的路
            res |= (path ^ best) << move  # 设置答案的每一位
            curNode = curNode.nexts[best]
        return res


def maxXorSubArray(alist):
    if alist == [] or len(alist) == 0:
        return 0
    maxNum, eor = 0, 0
    numTrie = NumTrie()
    numTrie.add(0)
    for i in range(len(alist)):
        eor ^= alist[i]
        maxNum = max(maxNum, numTrie.maxXor(eor))
        numTrie.add(eor)
    return maxNum


# 换钱的方法数,给定数组arr,arr中所有值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,
# 再给定一个整数aim代表要找的钱数,求换钱有多少种方法
def coins(alist, aim):
    if alist == [] or len(alist) == 0 or aim < 0:
        return 0
    # return coinsProcess(alist, 0, aim)
    return coinsProcess_map(alist, 0, aim)


def coinsProcess(alist, index, aim):
    """
    :param alist: 给定的正数数组
    :param index: 可以任意自由使用index及其以后所有的钱
    :param aim: 目标钱数
    :return: 方法数
    """
    res = 0
    if index == len(alist):
        res = 1 if aim == 0 else 0
    else:
        i = 0
        while i * alist[index] <= aim:
            res += coinsProcess(alist, index + 1, aim - i * alist[index])
            i += 1
    return res


# 无后效性:如果到达这个状态,和这个状态以后要完成的工作无影响。
#       比如[200, 100, 50, 10],目标金额1000,假设index=2,此时目标金额为600,使用index以后的金钱实现目标金额600的方法数和
#       如何达到index=2,aim=600的这种状态无关,即通过alist数组前2个数实现400这个过程和通过alist数组后2个数实现600这两个过程
#       互不影响、无关联。
# 一张200,两张100,后面需要完成600的目标,和两张200,没有100,后面完成600的目标。两个方法中,通过后两个实现600的目标方法数是一定的。
#       可以将其放在一个字典中,极大的节省了暴力递归时间
def coinsProcess_map(alist, index, aim):
    """  记忆化搜索
    :param alist: 给定的正数数组
    :param index: 可以任意自由使用index及其以后所有的钱
    :param aim: 目标钱数
    :return: 方法数
    """
    mapDict = {}  # key: "index_aim"  value: 返回值
    res = 0
    if index == len(alist):
        res = 1 if aim == 0 else 0
    else:
        i = 0
        while i * alist[index] <= aim:
            nextAim = aim - i * alist[index]
            keyStr = str(index + 1) + '_' + str(nextAim)
            if keyStr in mapDict.keys():
                res += mapDict[keyStr]
            else:
                res += coinsProcess_map(alist, index + 1, aim - i * alist[index])
            i += 1
    mapDict.update({str(index) + "_" + str(aim): res})
    return res


# 完全由暴力递归改为动态规划
def coins2(alist, aim):
    if alist == [] or len(alist) == 0 or aim < 0:
        return 0
    dp = np.zeros((len(alist) + 1, aim + 1))
    for i in range(1, aim + 1):
        dp[len(alist)][i] = 0  # 由终止条件确定。已经到了数组末尾,而aim>0,已经没有整数可用,仍想配成一定金额,所以方法数为0
    dp[len(alist)][0] = 1  # 由终止条件确定。当数组的数已经用完,此时目标数同时为0,返回方法数为1
    for i in range(len(alist) - 1, -1, -1):
        for j in range(0, aim + 1):
            k = 0
            while j - k * alist[i] >= 0:
                dp[i][j] += dp[i + 1][j - k * alist[i]]
                k += 1
    return dp[0][aim]


# 对初始版的动态规划优化一丢丢
def coins3(alist, aim):
    if alist == [] or len(alist) == 0 or aim < 0:
        return 0
    dp = np.zeros((len(alist) + 1, aim + 1))
    for i in range(aim + 1):
        dp[len(alist)][i] = 0
    dp[len(alist)][0] = 1
    for i in range(len(alist) - 1, -1, -1):
        for j in range(0, aim + 1):
            dp[i][j] = dp[i + 1][j]
            # dp[i][j] = dp[i + 1][j] + dp[i + 1][j - alist[i]] + dp[i + 1][j - 2 * alist[i]] + ...
            # dp[i][j - alist[i]] = dp[i + 1][j - alist[i]] + dp[i + 1][j - 2 * alist[i]] + ...
            # 因此 dp[i][j] = dp[i + 1][j] + dp[i][j - alist[i]]
            dp[i][j] += dp[i][j - alist[i]] if j - alist[i] >= 0 else 0
    return dp[0][aim]


def coins4(alist, aim):
    if alist == [] or len(alist) == 0 or aim < 0:
        return 0
    dp = np.zeros((len(alist), aim + 1))
    for i in range(len(alist)):
        dp[i][0] = 1
    j = 1
    # 不需要终止行,在第一行,只需要是5的倍数的aim,都可以实现,方法数仅是1(因为只用了一种金额的)
    while alist[0] * j <= aim:
        dp[0][alist[0] * j] = 1
        j += 1
    for i in range(1, len(alist)):
        for j in range(1, aim + 1):
            dp[i][j] = dp[i - 1][j]
            dp[i][j] += dp[i][j - alist[i]] if j - alist[i] >= 0 else 0
    return dp[len(alist) - 1][aim]


# 排成一条线的纸牌博弈问题
def winCard1(alist):
    if alist == [] or len(alist) == 0:
        return 0
    return max(f(alist, 0, len(alist) - 1), s(alist, 0, len(alist) - 1))


# alist[i:j]这个排列的纸牌被绝顶聪明的人先拿,最终能获得什么分数
def f(alist, i, j):
    """alist[i:j]这个排列的纸牌被绝顶聪明的人先拿,最终能获得什么分数"""
    if i == j:  # 如果仅剩一张,当然会被先拿的人拿走
        return alist[i]
    # 先拿的人拿的是alist[i],或者是alist[j],此时后拿的人因为同样是绝顶聪明的人,所以调用s()函数
    return max(alist[i] + s(alist, i + 1, j), alist[j] + s(alist, i, j - 1))


def s(alist, i, j):
    """alist[i:j]这个排列的纸牌被绝顶聪明的人后拿,最终能获得什么分数"""
    if i == j:  # 如果仅剩一张,肯定会被先拿的人拿走,后拿的人什么也拿不到
        return 0
    # 由于对手先拿,由于对方也是绝顶聪明的人,肯定会将最差的情况留给玩家。在对方拿完之后,此时玩家成了先拿的人
    return min(f(alist, i + 1, j), f(alist, i, j - 1))


def winCard2(alist):
    """由暴力递归改成动态规划"""
    if alist == [] or len(alist) == 0:
        return 0
    dpF = np.zeros((len(alist), len(alist)))
    dpS = np.zeros((len(alist), len(alist)))
    for j in range(len(alist)):
        dpF[j][j] = alist[j]
        for i in range(j - 1, -1, -1):
            dpF[i][j] = max(alist[i] + dpS[i + 1][j], alist[j] + dpS[i][j - 1])
            dpS[i][j] = min(dpF[i + 1][j], dpF[i][j - 1])
    return max(dpF[0][len(alist) - 1], dpS[0][len(alist) - 1])


# 机器人处于位置M(1<=M<=N)时,既可以向左走,也可以向右走(M=1时,只能向右,M=N时,只能向左)。经过P步后,走到位置K,一共有多少种方法
def ways(N, curPosition, restSteps, K):
    """
    :param N: 一共有1~N的位置
    :param curPosition:
    :param restSteps: 剩余走的步数
    :param K: 最终停留的位置
    :return: 一共有多少种走法
    """
    if N < 2 or curPosition < 1 or curPosition > N or restSteps < 0 or K < 1 or K > N:
        return 0
    if restSteps == 0:
        return 1 if curPosition == K else 0
    if curPosition == 1:
        res = ways(N, curPosition + 1, restSteps - 1, K)
    elif curPosition == N:
        res = ways(N, curPosition - 1, restSteps - 1, K)
    else:
        res = ways(N, curPosition + 1, restSteps - 1, K) + ways(N, curPosition - 1, restSteps - 1, K)
    return res


def ways2(N, curPosition, restSteps, K):
    if N < 2 or restSteps < 0 or K < 1 or K > N:
        return 0
    dp = np.zeros((restSteps + 1, N))
    dp[0][K - 1] = 1
    for i in range(1, restSteps + 1):
        for j in range(0, N):
            dp[i][j] = dp[i - 1][j - 1] if j - 1 >= 0 else 0
            dp[i][j] += dp[i - 1][j + 1] if j + 1 < N else 0
    return dp[restSteps][curPosition - 1]


def getMaxLength(alist, aim):
    if alist == [] or len(alist) == 0 or aim <= 0:
        return 0
    leftPoint, rightPoint, maxLen = 0, 0, 0
    sum = alist[0]
    while rightPoint < len(alist):
        if sum < aim:
            rightPoint += 1
            if rightPoint == len(alist):
                break
            sum += alist[rightPoint]
        elif sum == aim:
            maxLen = max(maxLen, rightPoint - leftPoint + 1)
            sum -= alist[leftPoint]
            leftPoint += 1
        else:
            sum -= alist[leftPoint]
            leftPoint += 1
    return maxLen


def getMinSteps(eggNum, floorNum):
    """
    扔鸡蛋问题
    :param eggNum:
    :param floorNum:
    :return:
    """
    if eggNum < 1 or floorNum < 1:
        return 0
    dp = np.zeros((eggNum + 1, floorNum + 1))
    for i in range(1, eggNum + 1):
        for j in range(1, floorNum + 1):
            dp[i][j] = j
    for i in range(2, eggNum + 1):
        for j in range(1, floorNum + 1):
            for k in range(1, j):
                dp[i][j] = min(dp[i][j], 1 + max(dp[i - 1][k - 1], dp[i][j - k]))
    return dp


# 从0位置开始向右扩,假如扩到位置T时,仍满足累加和<sum。但加上minSumList[T+1]时,>sum。此时不是从1位置重新开始,
# 而是在原有的基础上减去alist[1],看此时能否将minSumList[T+1]加上,因为我们求的是最长子数组的长度。这样做保证了右边界始终向右,
# 左边界也一直向右移动,做到了O(N).
def maxLengthAwesom(alist, aim):
    if alist == []:
        return 0
    if len(alist) == 1:
        return alist[0]
    # minSumList[i]: i开头的所有子数组的最小累加和
    # minSumIndexList[i]: 取得最小累加和的右边界
    minSumList, minSumIndexList = [], []
    minSumList.append(alist[-1])
    minSumIndexList.append(len(alist) - 1)
    for i in range(len(alist) - 2, -1, -1):
        if minSumList[0] < 0:
            minSumList.insert(0, alist[i] + minSumList[0])
            minSumIndexList.insert(0, minSumIndexList[0])
        else:
            minSumList.insert(0, alist[i])
            minSumIndexList.insert(0, i)
    end, sumNum, res = 0, 0, 0
    for i in range(len(alist)):
        while end < len(alist) and sumNum + minSumList[end] <= aim:
            sumNum += minSumList[end]
            end = minSumIndexList[end] + 1
        sumNum -= alist[i] if end > i else 0
        res = max(res, end - i)
        end = max(end, i + 1)
    return res


# 当仅剩最后一个时,修改其编号为1,因此,由公式推导它对应的在最一开始的编号。
# 旧编号(链表长度长度为i)和新编号(链表长度为i-1)对应的公式:旧 = (新 - 1 + S) % i + 1(S:被杀编号)
# 编号 = (报数 - 1) % i + 1
# 被杀编号:S = (m - 1) % i + 1
# --> 旧 = (新 + m - 1) % i + 1
# 当仅剩最后一个时,修改其编号为1,因此,由公式推导它对应的在最一开始的编号。
def getLive(Len, m):
    """
    :param Len: 总人数
    :param m: 报到m就杀人
    :return: 最后一个人
    """
    if Len == 1:
        return 1
    return (getLive(Len - 1, m) + m - 1) % Len + 1


# 环形单链表的约瑟夫问题,链表长度N,时间复杂度O(N)
def josephusKill2(pHead, m):
    if pHead is None or pHead.next == pHead or m < 1:
        return pHead
    cur = pHead.next
    tmp = 1  # 链表长度
    while cur != pHead:
        tmp += 1
        cur = cur.next
    tmp = getLive(tmp, m)  # 由最后一个生存者编号为1,根据公式回推在初始链表中的编号
    tmp -= 1
    while tmp != 0:
        pHead = pHead.next
        tmp -= 1
    pHead.next = pHead
    return pHead


def isMatch(str1, str2):
    """带有‘.’和‘*’的字符串匹配问题"""
    if str1 == "" or str2 == "":
        return False
    strList1, strList2 = [], []
    for i in str1:
        strList1.append(i)
    for i in str2:
        strList2.append(i)
    # return processMatchStr(strList1, strList2, 0, 0) if isValidStr(str1, str2) else False
    return processMatchStr(strList1, strList2, 0, 0) and isValidStr(str1, str2)


def isValidStr(str1, str2):
    """检查两个字符串的有效性,str1字符串不能有.和*,str2字符串首字符不能是*,不能连着两个字符都是*"""
    for i in str1:
        if i in '.*':
            return False
    if str2[0] == '*':
        return False
    for i in range(len(str2) - 1):
        if str2[i] == '*' and str2[i + 1] == '*':
            return False
    return True


def processMatchStr(strList1, strList2, start1, start2):
    if start2 == len(strList2):
        return start1 == len(strList1)
    if start2 + 1 == len(strList2) or strList2[start2 + 1] != '*':
        """start2后面接的不是'*'(或者start2已经指向了最后一个字符,或是start2后面的字符不是'*')
            在这种情况下,必须是三种情况都满足才会满足True。
                start1并没有越界
                此时两个字符串对应位置的字符相等,或是str2字符串对应的位置为'.'可以匹配任意的字符
                在start1,start2位置之后的字符串都能匹配上"""
        return start1 != len(strList1) and (strList1[start1] == strList2[start2] or strList2[start2] == '.') \
               and processMatchStr(strList1, strList2, start1 + 1, start2 + 1)
    """此时,start2+1位置是'*',分为两种情况讨论
            如果start1位置的字符和start2位置的字符不匹配,则start2+1位置的'*'起的作用是0个之前的字符,然后由start2+2位置的字符继续与start1位置的字符继续考虑匹配问题
                假设此时从start1位置开始的str1是:a...,从start2位置开始的str2是b*...,
            如果start1位置的字符和start2位置的字符匹配,则需要考虑start2+1位置的'*'所起的作用,是当做1倍、2倍、3倍...之前的字符进行匹配"""
    while start1 != len(strList1) and (strList1[start1] == strList2[start2] or strList2[start2] == '.'):
        """假设此时从start1位置开始的str1是:aaab...,从start2位置开始的str2是a*...
            首先考虑'*'只当做0倍,也就是str1从start1开始匹配,str2从start2+2开始匹配,如果不匹配
            则考虑'*'当做1倍考虑,也就是str1从start1+1开始匹配,str2从start2+2开始匹配,如果不匹配
            则考虑'*'当做2倍考虑,也就是str2从start1+1开始匹配,str2从start2+2开始匹配,如果不匹配
            ...
            直到匹配或者某一个越界为止
        """
        if processMatchStr(strList1, strList2, start1, start2 + 2):
            return True
        start1 += 1
    return processMatchStr(strList1, strList2, start1, start2 + 2)


# dp(横轴:exp(范围[0, len(exp)]), 纵轴: str(范围[0, len(str)])))
# 由原题意可知,dp[i][j]与dp[i + 1][j + 1]、dp[i][j + 2]、dp[i++][j + 2]有关,因此,需要知道dp表中最后两列和最后一排的数,才能推出整张表
# 最后一列的意义:j=len(exp),已经没有字符可用了,此时str仍有待匹配项,所以全部为False(除了i=len(str),j=len(exp),此时匹配项和待匹配项整好全部耗尽,此位置为True)
# 倒数第二列的意义:此时exp仅剩最后一个字符,而待匹配项还有好多,全部为False。只有str[len(str)-1]==exp[len(exp)-1],或者exp[len(exp)-1]='.'时该位置为True,其他位置全为False
# 最后一排的意义:此时待匹配项str已经没有字符需要被匹配,而匹配项还有好多。只有匹配项满足'a*a*a*...',这样才会为True,否则是False
# 当推出普遍位置后,发现basicase不够,需要还原到原题意,把欠缺的位置补上
def isMatchDPMatrix(str1, str2):
    if str1 == "" or str2 == "":
        return False
    strList1, strList2 = [], []
    for i in str1:
        strList1.append(i)
    for i in str2:
        strList2.append(i)
    if not isValidStr(str1, str2):
        return False
    dpMatrix = initDPMap(strList1, strList2)
    for i in range(len(strList1) - 1, -1, -1):
        for j in range(len(strList2) - 2, -1, -1):
            if strList2[j + 1] != '*':
                dpMatrix[i][j] = (strList1[i] == strList2[j] or strList2[j] == '.') and dpMatrix[i + 1][j + 1]
            else:
                si = i
                while si != len(strList1) and (strList1[si] == strList2[j] or strList2[j] == '.'):
                    if dpMatrix[si][j + 2]:
                        dpMatrix[i][j] = 1
                        break
                    si += 1
                if dpMatrix[i][j] != 1:
                    dpMatrix[i][j] = dpMatrix[si][j + 2]
    return dpMatrix[0][0]


def initDPMap(strList1, strList2):
    strLen1, strLen2 = len(strList1), len(strList2)
    dpMatrix = np.zeros((strLen1 + 1, strLen2 + 1))
    dpMatrix[strLen1][strLen2] = 1
    for j in range(strLen2 - 2, -1, -2):
        # 最后一排要满足'a*a*a*...'这种情况,才会为True,否则为False
        if strList2[j] != '*' and strList2[j + 1] == '*':
            dpMatrix[strLen1][j] = 1
        else:
            break
    if strLen1 > 0 and strLen2 > 0:
        if strList2[strLen2 - 1] == '.' or strList1[strLen1 - 1] == strList2[strLen1 - 1]:
            # 此时str1还剩最后一个待匹配项,str2也仅剩最后一个匹配项。
            dpMatrix[strLen1 - 1][strLen2 - 1] = 1
    return dpMatrix
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值