LeetCode 488 Zuma Game 解题报告

原文链接: http://hankerzheng.com/blog/Leetcode-Zuma-Game-

Problem Description

LeetCode 488 Zuma Game
Think about Zuma Game. You have a row of balls on the table, colored red(R), yellow(Y), blue(B), green(G), and white(W). You also have several balls in your hand.

Each time, you may choose a ball in your hand, and insert it into the row (including the leftmost place and rightmost place). Then, if there is a group of 3 or more balls in the same color touching, remove these balls. Keep doing this until no more balls can be removed.

Find the minimal balls you have to insert to remove all the balls on the table. If you cannot remove all the balls, output -1.

Examples:

Input: “WRRBBW”, “RB”
Output: -1
Explanation: WRRBBW -> WRR[R]BBW -> WBBW -> WBB[B]W -> WW

Input: “WWRRBBWW”, “WRBRW”
Output: 2
Explanation: WWRRBBWW -> WWRR[R]BBWW -> WWBBWW -> WWBB[B]WW -> WWWW -> empty

Input:”G”, “GGGGG”
Output: 2
Explanation: G -> G[G] -> GG[G] -> empty

Input: “RBYYBBRRB”, “YRBGB”
Output: 3
Explanation: RBYYBBRRB -> RBYY[Y]BBRRB -> RBBBRRB -> RRRB -> B -> B[B] -> BB[B] -> empty

Note:
You may assume that the initial row of balls on the table won’t have any 3 or more consecutive balls with the same color.
The number of balls on the table won’t exceed 20, and the string represents these balls is called “board” in the input.
The number of balls in your hand won’t exceed 5, and the string represents these balls is called “hand” in the input.
Both input strings will be non-empty and only contain characters ‘R’,’Y’,’B’,’G’,’W’.

中文大意:
祖玛游戏, 也就是QQ龙珠, 告诉你当前桌面上球的排列顺序以及你当前手上有的球, 求问当前手上的球是否能够完全消除桌面上的球, 如果能, 返回所需要球的最小个数; 如果不能, 返回-1

My Solution

English Version Post on LeetCode Discussion

Naive Solution (暴力解)

根据当前手上求球的颜色, 每次消除桌面上相同颜色的球, 然后递归, 直到桌面上没球了或者手上没球了. 最后统计当桌面上没球的时候, 花费球的数量.

暴力解, 很直接, 可以直接用backtracking来做. 但是问题在于每次消除一个颜色的球之后, 我们需要重构当前桌面的球, 比如如果此时桌面上面有 Y RR GG R YY, 我们手上有 G R Y. 当我们消除R的时候我们需要重构桌面上的球, 并且作为参数传递, 递归调用; 当我们消除G的时候, 我们不仅仅需要重构, 还需要判断当前桌面的球是否能自我销毁.

因此暴力解的每次递归都是一个O(n)时间复杂度的操作, 而暴力解本身是一个expoential solution, 两者一相乘, 这时间复杂度太美不敢看了.

Dynamic Programming Solution

首先预处理input string, 将桌面上的球转换为一个列表, 列表的每个元素表示连续相同颜色球的颜色和个数, 并且预处理时, 可以直接消除相同颜色超过三个以上的球. 因此, 对于RRBBBGYYWWWYB, 转化后的表示为[["R", 2], ["B", 3], ["G", 1], ["B", 1]].
当前问题的最优解有以下三种可能性:
- 将当前区间分割为两个部分, 两个部分的最优解合并, 即可得到当前问题的解
- 如果当前区间第一个颜色和最后一个颜色相同, 那么第一个色块和最后一个色块可以最后合并. 当前问题的解, 即为中间区间的最优解, 加上第一个色块和最后一个色块合并后新问题的解.
- 如果当前区间第一个颜色和最后一个颜色相同, 并且两个色块合并后个数小于3, 并且存在中间相同颜色个数为1的色块, 那么这三个分离的色块可以合并并消除. 因此, 问题的解即为分割后, 剩下两个色块的最优解.

上述三种情况涵盖了所有的可能性. 该算法的时间复杂度为O(N^3), 其中N表示色块的个数.

class Solution(object):
    def findMinStep(self, board, hand):
        """
        :type board: str
        :type hand: str
        :rtype: int
        """
        def getBalls(balls):
            """
            Convert the init given board string into a ball list.
            Each element of the list is in the form of [color, ballCnt]
            This function can automatically clear the 3 consective balls with
            the same color in the given string.

            >>> getBalls("RRBBBGYYWWWYB")
            [["R", 2], ["B", 3], ["G", 1], ["B", 1]]
            """
            ballList = []
            for ball in balls:
                if not ballList or ballList[-1][0] != ball:
                    if ballList and ballList[-1][1] >= 3:
                        ballList.pop(-1)
                    ballList.append([ball, 1])
                elif ball == ballList[-1][0]:
                    ballList[-1][1] += 1
            return ballList

        def combineBalls(balls1, balls2):
            """
            Combine 2 sets of balls together.

            >>> combineBalls({"R": 1}, {"R": 1, "G": 1})
            {"R": 2, "G": 1}
            """
            ans = dict(balls1)
            for key, value in balls2.items():
                if key in ans:
                    ans[key] += value
                else:
                    ans[key] = value
            return ans

        def cntBalls(balls):
            """
            Count the number of balls we have chosen.
            Since there is only 5 colors in the game, this function can be done in O(1) time.
            """
            return sum(balls.values())

        def updateAns(ans1, ans2):
            """
            Compare two different solution to the sub-problem,
            and return the better one.
            If `ans1` has fewer balls and `ans1` can be formed by the balls given,
            then just return `ans1`, else we return `ans2`
            Therefore, `ans1` should always be the new soluton, while `ans2` the old.
            """
            if cntBalls(ans1) < cntBalls(ans2) and checkAvailable(ans1, ballsInHand) >= 0:
                return ans1
            return ans2

        def checkAvailable(balls, ballsInHand):
            """
            Check whether current balls is available according to the given balls.
            Since there is only 5 colors in the game, this function can be done in O(1) time.
            """
            for key, value in balls.items():
                if balls[key] != 0:
                    if key not in ballsInHand:
                        return -1
                    if ballsInHand[key] < value:
                        return -1
            return sum(balls.values())

        def memorySearch(start, end):
            if end < start:
                return {}
            elif (start, end) in history:
                return history[(start, end)]
            elif start == end:
                return {ballsTable[start][0]: 3 - ballsTable[start][1]}
            elif start + 1 == end:
                return combineBalls(memorySearch(start, start), memorySearch(end, end))

            thisAns = {"R":float("inf")}
            firstColor, lastColor = ballsTable[start][0], ballsTable[end][0]
            # The first possible Solution is to split the balls into 2 parts and finish both of them seperately
            for k in xrange(start, end):
                thisBalls = combineBalls(memorySearch(start, k), memorySearch(k+1, end))
                thisAns = updateAns(thisBalls, thisAns)
            # The second possible Solution is to clear the first and the last balls in the end
            if firstColor == lastColor:
                toAdd = max(0, 3 - ballsTable[start][1] - ballsTable[end][1])
                thisBalls = combineBalls(memorySearch(start+1, end-1), {firstColor: toAdd})
                thisAns = updateAns(thisBalls, thisAns)
            # The third possible Solution is to clear the first and the last balls in the end with
            # one ball in the middle
            if firstColor == lastColor and 1 in (ballsTable[start][1], ballsTable[end][1]):
                idx = start + 1
                while idx < end:
                    if ballsTable[idx][0] == firstColor and ballsTable[idx][1] == 1:
                        thisBalls = combineBalls(memorySearch(start + 1, idx - 1), memorySearch(idx + 1, end - 1))
                        thisAns = updateAns(thisBalls, thisAns)
                    idx += 1
            history[(start, end)] = thisAns
            return thisAns

        # Initialization
        ballsTable = getBalls(board)
        ballsInHand = {}
        for ball in hand:
            ballsInHand[ball] = ballsInHand.get(ball, 0) + 1
        history = {}
        length = len(ballsTable)
        return checkAvailable(memorySearch(0, length - 1), ballsInHand)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值