线性动态规划问题解析:矩阵与无串类型

线性动态规划问题解析:矩阵与无串类型

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

动态规划(Dynamic Programming, DP)是解决最优化问题的重要方法之一。本文将深入探讨线性动态规划中的两类典型问题:矩阵线性DP和无串线性DP,帮助读者掌握这两种问题的解题思路和实现方法。

矩阵线性DP问题

矩阵线性DP问题是指输入为二维矩阵的线性动态规划问题。这类问题的状态通常定义为dp[i][j],表示从矩阵的某个起始位置(通常是(0,0))到达位置(i,j)的相关解。

最小路径和问题

问题描述

给定一个包含非负整数的m×n网格,找出一条从左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或向右移动一步。

解题思路
  1. 状态定义:dp[i][j]表示从(0,0)到达(i,j)的最小路径和。

  2. 状态转移方程

    • 当前位置只能从上方或左方到达
    • dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
  3. 边界条件

    • 第一行只能从左方到达
    • 第一列只能从上方到达
  4. 实现要点

    • 初始化第一行和第一列
    • 按行或列顺序填充dp表
代码实现
def minPathSum(grid):
    m, n = len(grid), len(grid[0])
    dp = [[0]*n for _ in range(m)]
    
    dp[0][0] = grid[0][0]
    for i in range(1, m):
        dp[i][0] = dp[i-1][0] + grid[i][0]
    for j in range(1, n):
        dp[0][j] = dp[0][j-1] + grid[0][j]
    
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
    
    return dp[-1][-1]
复杂度分析
  • 时间复杂度:O(mn),需要遍历整个网格
  • 空间复杂度:O(mn),需要存储dp表

最大正方形问题

问题描述

给定一个由'0'和'1'组成的二维矩阵,找到只包含'1'的最大正方形,并返回其面积。

解题思路
  1. 状态定义:dp[i][j]表示以(i,j)为右下角的最大正方形边长。

  2. 状态转移方程

    • 如果matrix[i][j]为'0',dp[i][j]=0
    • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
  3. 边界条件

    • 第一行和第一列的最大边长只能是0或1
  4. 实现要点

    • 初始化时处理边界情况
    • 维护一个变量记录最大边长
代码实现
def maximalSquare(matrix):
    if not matrix: return 0
    
    m, n = len(matrix), len(matrix[0])
    dp = [[0]*(n+1) for _ in range(m+1)]
    max_len = 0
    
    for i in range(1, m+1):
        for j in range(1, n+1):
            if matrix[i-1][j-1] == '1':
                dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
                max_len = max(max_len, dp[i][j])
    
    return max_len * max_len
复杂度分析
  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)

无串线性DP问题

无串线性DP问题是指问题的输入不是显式的数组或字符串,但依然可以分解为若干子问题的线性动态规划问题。

整数拆分问题

问题描述

给定一个正整数n,将其拆分为k(k≥2)个正整数的和,并使这些整数的乘积最大化,返回可以获得的最大乘积。

解题思路
  1. 状态定义:dp[i]表示将整数i拆分为至少两个正整数的和后的最大乘积。

  2. 状态转移方程

    • 对于每个i,尝试所有可能的拆分点j(1≤j<i)
    • dp[i] = max(j*(i-j), j*dp[i-j]),对所有j取最大值
  3. 边界条件

    • dp[0] = dp[1] = 0
  4. 实现要点

    • 从2开始逐步计算dp值
    • 内层循环尝试所有可能的拆分
代码实现
def integerBreak(n):
    dp = [0]*(n+1)
    for i in range(2, n+1):
        for j in range(1, i):
            dp[i] = max(dp[i], j*(i-j), j*dp[i-j])
    return dp[n]
复杂度分析
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

只有两个键的键盘问题

问题描述

最初记事本上只有一个字符'A'。每次可以进行两种操作:

  1. 复制全部字符
  2. 粘贴上一次复制的字符

求得到恰好n个'A'的最少操作次数。

解题思路
  1. 状态定义:dp[i]表示得到i个'A'的最少操作次数。

  2. 状态转移方程

    • 对于每个i,寻找其所有因子j
    • dp[i] = min(dp[j] + i//j, dp[i//j] + j)
  3. 边界条件

    • dp[1] = 0
  4. 实现要点

    • 只需检查小于等于√i的因子
    • 同时考虑j和i//j两种情况
代码实现
import math

def minSteps(n):
    dp = [0]*(n+1)
    for i in range(2, n+1):
        dp[i] = float('inf')
        for j in range(1, int(math.sqrt(i))+1):
            if i % j == 0:
                dp[i] = min(dp[i], dp[j] + i//j, dp[i//j] + j)
    return dp[n]
复杂度分析
  • 时间复杂度:O(n√n)
  • 空间复杂度:O(n)

总结

线性动态规划是动态规划中最基础也最重要的一类问题。通过本文对矩阵线性DP和无串线性DP两类问题的详细解析,我们可以总结出以下解题要点:

  1. 明确状态定义:根据问题特点,准确定义dp数组的含义。

  2. 确定状态转移:分析问题如何从子问题转移而来,写出正确的状态转移方程。

  3. 处理边界条件:特别注意初始条件和边界情况的处理。

  4. 优化空间复杂度:在可能的情况下,考虑使用滚动数组等技巧优化空间使用。

  5. 验证正确性:通过小规模测试用例验证算法的正确性。

掌握这些线性DP问题的解题思路,能够帮助我们更好地理解和解决更复杂的动态规划问题。

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

纪越岩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值