【经典题目】戳气球——dp问题的遍历方法

引言

戳气球问题是dp问题中的一道经典问题,难度在于逆向的思维和复杂的dp遍历方法。
题目链接添加链接描述

题目

【经典题目】戳气球——dp问题的遍历方法_第1张图片

设计状态和转移方程

首先如果是正向思维,其实很难求解。难点在于定义状态,如何定义第i个和第j个气球已经被戳破的状态。我首先想到的方法是数位dp的方法,设置有n个二进制数字,初始化全1,如果扎破了该位就置0,这样是可以定义状态的。但是这道题目的n很大,很难进行实现。

因此第一个难点出现了,考虑逆向的思维,开始添加气球。按照什么样的顺序添加气球才能够得到最大的金币。因此我们可以考虑这么一个子问题,假设最后一个戳破的气球是K号气球,则得到这个状态的的前提是,K气球的两侧气球全部都被戳破了已经。

因此我们定义状态dp[left][right]为开区间 ( l e f t , r i g h t ) (left, right) (left,right)中戳破所以气球的最大收益,这里我们需要对原数组增补两个1在头尾。

因此可以得到转移方程dp[left][right] = dp[k][right] + dp[left][k] + nums[left]*nums[right]*nums[k],其中,k在[left+1, right-1]之间遍历,选择最大值。

遍历方法

dp问题还有一个难点在于状态转移所依赖的状态必须被提前计算出来。从上面的转移方程可以看到,dp[left][right]需要依赖dp[left][i]dp[i][right]。参考labuladong的图。
【经典题目】戳气球——dp问题的遍历方法_第2张图片

其实左上角的图应该是dp[0][n-1],然后dp[i][i], dp[i][i+1] = 0。因此可以有两种遍历方法,一种是从下往上,一种是斜向。

代码上我也分别写出:

从下往上:

class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        nums = [1]+nums+[1]
        ans = 0
        n = len(nums)
        dp = [[0]*n for _ in range(n)]
        # 在进行遍历的时候需要注意,新的状态需要从已经求好的状态和baseline中得到
        ## 表格中,横轴表示left,纵轴表示right
        ## 遍历方法1:从下往上,dp[left][right]都表示开区间,因此从n-2开始就可以
        for left in range(n-2, -1, -1):
            ## 同样right从left+2开始才有必要更新
            for right in range(left+2, n):
                ## 再去遍历最后一个提取出来的数值K
                for k in range(left+1,right):
                    dp[left][right] = max(dp[left][right],dp[left][k]+dp[k][right]+nums[left]*nums[k]*nums[right])
        return dp[0][n-1]

斜向遍历:

斜向遍历需要引入一个宽度,也即是当前遍历的斜向距离对角线的距离。

class Solution(object):
    def maxCoins(self, nums):
        ## 采用斜向的遍历,需要维护一个跨度变量
        # k是宽度,从2开始,层层迭代
        nums = [1]+nums+[1]
        ans = 0
        n = len(nums)
        dp = [[0]*n for _ in range(n)]
        for k in range(2, n):
            for left in range(n - k):
                right = left + k
                for t in range(left + 1, right):
                    dp[left][right] = max(dp[left][right], nums[right] * nums[t] * nums[left] + dp[left][t] + dp[t][right])
        return dp[0][n-1]

总结

至此这个题目结束,可以说代码简洁,但是并不简单,需要巧妙的定义dp的状态和采用不同需寻常遍历方法。值得总结。

你可能感兴趣的:(leetcode经典题目)