教你一文破解连续区间分割问题:手撕矩阵链乘法与戳气球难题

一 矩阵链乘法问题

1.1 基本知识

1.1 两个矩阵的乘法复杂度:
当两个矩阵A(p,q),B(q,r)进行乘法时,C(p,r) = AxB,时间复杂度是O(pqr)

在这里插入图片描述
1.2 三个及多个矩阵的复杂度:
从下图看出三个矩阵进行乘法时,在不同位置进行结合,他们之间的复杂度不同!
在这里插入图片描述
那么当有多个矩阵进行乘法时,是否能找到计算效率最高的方法?
也就是让一下问题:
如何确定相乘顺序(给矩阵链加括号),提高计算效率?

在这里插入图片描述

1.2 矩阵链乘法问题

由以上的铺垫,下面直接给出矩阵链乘法问题的动态规划转移方程。

在这里插入图片描述
1) 动态规划转移方程:
在这里插入图片描述
2) 初始化
对dp table初始化,𝒊 = 𝒋时,矩阵链只有一个矩阵,乘法次数为0。由于是求最小值问题,其余先初始化为inf
在这里插入图片描述

3)确定计算顺序
由dp table可看出,最终的解依赖子问题的最优解,因此计算顺序是自底向上计算。最终的最优解是dp[0][n],就是右上角位置。对于这种问题,又可以分为两个方法来计算。 #但这两种方法时间复杂度都是O(n^3)

(1):斜线遍历计算
(2):从下到上从左到右遍历计算

在这里插入图片描述
(1):斜线遍历计算
在这里插入图片描述
(2):从下到上从左到右遍历计算
在这里插入图片描述

1.3 代码实现

(1)动态规划:斜线遍历计算

class Solution2: # 时间复杂度O(n^3)
    def MatrixChainMultiply(self, p, n):
        D = [[float('inf')]*(n) for _ in range(n)]
        rec = [[0]*(n) for _ in range(n)]
        for i in range(n):
            D[i][i] = 0
        # 动态规划, 相当于斜着遍历!
        for l in range(2, n+1):  # 区间长度从小到大
            # 以此计算子问题
            for i in range(0, n-l+1):
                j = i + l - 1
                # print(i,j)
                for k in range(i, j):  # 枚举所有分割位置
                    # 计算最少乘法次数
                    q = D[i][k] + D[k + 1][j] + p[i - 1] * p[k] * p[j]
                    D[i][j] = min(D[i][j], q)

        return D[0][n-1], D

if __name__ == "__main__":
    p = [2, 3, 7, 9, 5, 2, 4, 8, 10, 12, 6, 5]
    n = len(p) - 1

    count, D = Solution2().MatrixChainMultiply(p, n)
    print("--------无决策记录+斜线遍历--------")
    print("最少计算次数:", count)
    # for i in D:
    #     print(i)

--------无决策记录+斜线遍历--------
最少计算次数: 954

(2)动态规划:下上->左右遍历

class Solution2: # 时间复杂度O(n^3)
    def MatrixChainMultiply(self, p, n):
        D = [[float('inf')]*(n) for _ in range(n)]
        rec = [[0]*(n) for _ in range(n)]
        for i in range(n):
            D[i][i] = 0
        # 动态规划,下上->左右遍历!
        for i in range(n-2, -1,-1):  # i从下到上遍历,倒数第二行开始
            for j in range(i, n):    # j从左到右遍历
                # print(i,j)
                for k in range(i, j):  # 枚举所有分割位置
                    # 计算最少乘法次数
                    q = D[i][k] + D[k + 1][j] + p[i - 1] * p[k] * p[j]
                    D[i][j] = min(D[i][j], q)

        return D[0][n-1], D

if __name__ == "__main__":
    p = [2, 3, 7, 9, 5, 2, 4, 8, 10, 12, 6, 5]
    n = len(p) - 1

    count, D = Solution2().MatrixChainMultiply(p, n)
    print("--------无决策记录+下上->左右遍历--------")
    print("最少计算次数:", count)
    # for i in D:
    #     print(i)

输出结果

--------无决策记录+下上->左右遍历--------
最少计算次数: 954

(3) 最优方案追踪

下面这种方法是输出具体是如何增加进行加括号的方法。只需增加rec数组,然后进行方案追踪。更为详细的过程请看:算法设计与分析—童咏昕 https://www.icourse163.org/learn/BUAA-1449777166?tid=1463474515#/learn/content?type=detail&id=1241406029&sm=1

# 矩阵链乘法问题
class Solution: # 时间复杂度O(n^3)
    def MatrixChainMultiply(self, p, n):
        D = [[float('inf')]*(n) for _ in range(n)]
        rec = [[0]*(n) for _ in range(n)]
        for i in range(n):
            D[i][i] = 0
        # 动态规划
        for l in range(2, n+1):  # 区间长度从小到大
            # 以此计算子问题
            for i in range(0, n-l+1):
                j = i + l - 1
                # print(i,j)
                for k in range(i, j):  # 枚举所有分割位置
                    # 计算最少乘法次数
                    q = D[i][k] + D[k+1][j] + p[i-1]*p[k]*p[j]
                    if q < D[i][j]:
                        D[i][j] = q
                        rec[i][j] = k  # 记录最优决策
        return D[0][n-1], rec, D

# 最优方案追踪
# 输入矩阵链U1_6,追踪数组rec,位置索引i,j
# 输出矩阵链的加括号方式
def printMatrixChain(U, rec, i, j):
    if i == j:
        print(U[i], end='')
        return
    print("(", end='')
    printMatrixChain(U, rec, i, rec[i][j])
    print(")(", end='')
    printMatrixChain(U, rec, rec[i][j] + 1, j)
    print(")", end='')
    return

if __name__ == "__main__":
    p = [2, 3, 7, 9, 5, 2, 4, 8, 10, 12, 6, 5]
    n = len(p) - 1

    count, rec, D = Solution().MatrixChainMultiply(p, n)
    print("最少计算次数:", count)
    # for i in D:
    #    print(i)

    print("--------最优方案--------")
    U = '1234567890a'  # 每个字符相当于一个矩阵
    i, j = 0, n - 1
    printMatrixChain(U, rec, i, j)
    print()     

输出结果

最少计算次数: 954
--------最优方案--------
(1)(((((((2)((3)((4)((5)(6)))))(7))(8))(9))(0))(a))

二 LeetCode题

2.1 题目描述

312. 戳气球

在这里插入图片描述

2.2 代码实现

通过对矩阵链乘法问题的学习,戳气球就能迎刃而解了。需要注意的是,这里的状态转移方程与前面稍微有点差别:
戳气球:dp[i][j] = max(dp[i][j],dp[i][k] + dp[k][j] +points[i] * points[j] *points[k]

而矩阵链的是:D[i][j] = min(D[i][j], D[i][k] + D[k + 1][j] + p[i - 1] * p[k] * p[j])

这里不是说他们求问题的解不同(一个是最小值,一个是最大值),当然这也是一个方面,而主要是:他们的子问题有点不同。

戳气球的子问题是dp[i][k]dp[k][j]

矩阵链是:D[i][k]D[k + 1][j]

就是他们后面一项不同,这里要注意。所以在遍历计算dp table的时候就要注意细节。计算顺序同样也是自底向上,分为斜线遍历与从下到上从左到右遍历。

(1)动态规划:斜线遍历

class Solution:
    def maxCoins(self, nums):
        ns = [1] + nums + [1]
        N = len(ns)
        dp = [[0] * N for _ in range(N)]
        for l in range(N):
            for i in range(N-l):
                j = i + l
                for k in range(i+1,j):
                    dp[i][j] = max(dp[i][j], \
                        dp[i][k]+dp[k][j]+ns[i]*ns[k]*ns[j])
        return dp[0][-1]

输出结果

------戳气球-------
167

力扣提交结果:

在这里插入图片描述

(2) 动态规划:从下到上从左到右遍历

class Solution:
    def maxCoins(self, nums):
        n = len(nums)
        # 添加两侧的虚拟气球
        points = [1]  + nums + [1]

        # base case初始化为0
        dp = [[0] * (n+2) for _ in range(n+2)]
        # 开始状态转移,
        for i in range(n, -1, -1):  # i从下往上遍历,倒数第二行开始遍历
            for j in range(i+1, n+2):   # j从左往右遍历
                # 戳破的气球的位置
                for k in range(i+1, j):
                    # 择优做选择
                    dp[i][j] = max(
                        dp[i][j],
                        dp[i][k] + dp[k][j] +
                        points[i] * points[j] * points[k])
        return dp[0][-1], dp
nums = [3,1,5,8]

print("------戳气球-------")
coins, dp = Solution().maxCoins(nums)
print(coins)
for row in dp:
    print(row)

输出结果

------戳气球-------
167

力扣提交结果:
在这里插入图片描述

参考文章&资料

  1. 算法设计与分析—童咏昕 https://www.icourse163.org/learn/BUAA-1449777166?tid=1463474515#/learn/content?type=detail&id=1241406029&sm=1
  2. https://leetcode-cn.com/problems/burst-balloons/
  3. https://mp.weixin.qq.com/s/I0yo0XZamm-jMpG-_B3G8g
  4. https://leetcode-cn.com/problems/burst-balloons/solution/yi-wen-tuan-mie-qu-jian-dp-by-bnrzzvnepe-2k7b/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄波波19

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值