线性动态规划问题解析:矩阵与无串类型
动态规划(Dynamic Programming, DP)是解决最优化问题的重要方法之一。本文将深入探讨线性动态规划中的两类典型问题:矩阵线性DP和无串线性DP,帮助读者掌握这两种问题的解题思路和实现方法。
矩阵线性DP问题
矩阵线性DP问题是指输入为二维矩阵的线性动态规划问题。这类问题的状态通常定义为dp[i][j],表示从矩阵的某个起始位置(通常是(0,0))到达位置(i,j)的相关解。
最小路径和问题
问题描述
给定一个包含非负整数的m×n网格,找出一条从左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或向右移动一步。
解题思路
-
状态定义:dp[i][j]表示从(0,0)到达(i,j)的最小路径和。
-
状态转移方程:
- 当前位置只能从上方或左方到达
- dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
-
边界条件:
- 第一行只能从左方到达
- 第一列只能从上方到达
-
实现要点:
- 初始化第一行和第一列
- 按行或列顺序填充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'的最大正方形,并返回其面积。
解题思路
-
状态定义:dp[i][j]表示以(i,j)为右下角的最大正方形边长。
-
状态转移方程:
- 如果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
-
边界条件:
- 第一行和第一列的最大边长只能是0或1
-
实现要点:
- 初始化时处理边界情况
- 维护一个变量记录最大边长
代码实现
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)个正整数的和,并使这些整数的乘积最大化,返回可以获得的最大乘积。
解题思路
-
状态定义:dp[i]表示将整数i拆分为至少两个正整数的和后的最大乘积。
-
状态转移方程:
- 对于每个i,尝试所有可能的拆分点j(1≤j<i)
- dp[i] = max(j*(i-j), j*dp[i-j]),对所有j取最大值
-
边界条件:
- dp[0] = dp[1] = 0
-
实现要点:
- 从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'。每次可以进行两种操作:
- 复制全部字符
- 粘贴上一次复制的字符
求得到恰好n个'A'的最少操作次数。
解题思路
-
状态定义:dp[i]表示得到i个'A'的最少操作次数。
-
状态转移方程:
- 对于每个i,寻找其所有因子j
- dp[i] = min(dp[j] + i//j, dp[i//j] + j)
-
边界条件:
- dp[1] = 0
-
实现要点:
- 只需检查小于等于√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两类问题的详细解析,我们可以总结出以下解题要点:
-
明确状态定义:根据问题特点,准确定义dp数组的含义。
-
确定状态转移:分析问题如何从子问题转移而来,写出正确的状态转移方程。
-
处理边界条件:特别注意初始条件和边界情况的处理。
-
优化空间复杂度:在可能的情况下,考虑使用滚动数组等技巧优化空间使用。
-
验证正确性:通过小规模测试用例验证算法的正确性。
掌握这些线性DP问题的解题思路,能够帮助我们更好地理解和解决更复杂的动态规划问题。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考