动态规划进阶:多维DP深度解析

1. 多维DP概述

多维DP是指状态定义涉及两个以上维度的动态规划问题。这类问题通常需要在二维、三维甚至更高维度的状态空间中进行状态转移,常用于处理网格、矩阵、三维空间等复杂问题。

2. 多维DP基本概念

2.1 多维DP特点

  • 多维度状态 :状态通常表示为 dp[i][j][k]...
  • 复杂转移:状态转移可能涉及多个维度的变化
  • 空间消耗大:需要仔细设计以优化空间复杂度
  • 遍历顺序重要:需要确保所有依赖状态都已计算

2.2 通用模板

python 复制代码
def multi_dp_template(m, n, p):
    """
    三维DP通用模板
    """
    # 初始化三维DP数组
    dp = [[[0] * p for _ in range(n)] for _ in range(m)]
    
    # 初始化边界条件
    for i in range(m):
        for j in range(n):
            dp[i][j][0] = base_value_1
    
    for j in range(n):
        for k in range(p):
            dp[0][j][k] = base_value_2
    
    # 状态转移
    for i in range(1, m):
        for j in range(1, n):
            for k in range(1, p):
                dp[i][j][k] = max/min(
                    dp[i-1][j][k] + cost1,
                    dp[i][j-1][k] + cost2,
                    dp[i][j][k-1] + cost3,
                    # ... 其他转移
                )
    
    return dp[m-1][n-1][p-1]

3. 二维网格DP进阶

3.1 最小路径和 (LeetCode 64)

问题描述:在网格中从左上角到右下角,求最小路径和。

Python实现
python 复制代码
def minPathSum(grid):
    """
    最小路径和 - 基础版本
    """
    if not grid or not grid[0]:
        return 0
    
    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[m-1][n-1]

#### 空间优化版本
def minPathSum_optimized(grid):
    """
    空间优化:只使用一行DP数组
    """
    if not grid or not grid[0]:
        return 0
    
    m, n = len(grid), len(grid[0])
    dp = [0] * n
    
    # 初始化第一行
    dp[0] = grid[0][0]
    for j in range(1, n):
        dp[j] = dp[j-1] + grid[0][j]
    
    # 更新后续行
    for i in range(1, m):
        dp[0] += grid[i][0]  # 第一列
        for j in range(1, n):
            dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
    
    return dp[n-1]

#### 原地修改版本
def minPathSum_inplace(grid):
    """
    原地修改:使用输入网格存储DP值
    """
    if not grid or not grid[0]:
        return 0
    
    m, n = len(grid), len(grid[0])
    
    # 初始化第一行
    for j in range(1, n):
        grid[0][j] += grid[0][j-1]
    
    # 初始化第一列
    for i in range(1, m):
        grid[i][0] += grid[i-1][0]
    
    # 状态转移
    for i in range(1, m):
        for j in range(1, n):
            grid[i][j] += min(grid[i-1][j], grid[i][j-1])
    
    return grid[m-1][n-1]
Java实现
java 复制代码
public class MinimumPathSum {
    // 标准DP解法
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][n];
        
        dp[0][0] = grid[0][0];
        
        // 初始化第一列
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i-1][0] + grid[i][0];
        }
        
        // 初始化第一行
        for (int j = 1; j < n; j++) {
            dp[0][j] = dp[0][j-1] + grid[0][j];
        }
        
        // 状态转移
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }
        
        return dp[m-1][n-1];
    }
    
    // 空间优化版本
    public int minPathSumOptimized(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        
        int m = grid.length, n = grid[0].length;
        int[] dp = new int[n];
        
        dp[0] = grid[0][0];
        for (int j = 1; j < n; j++) {
            dp[j] = dp[j-1] + grid[0][j];
        }
        
        for (int i = 1; i < m; i++) {
            dp[0] += grid[i][0];
            for (int j = 1; j < n; j++) {
                dp[j] = Math.min(dp[j], dp[j-1]) + grid[i][j];
            }
        }
        
        return dp[n-1];
    }
}

3.2 不同路径带障碍物 (LeetCode 63)

问题描述:网格中有障碍物,求从左上到右下的不同路径数。

python 复制代码
def uniquePathsWithObstacles(obstacleGrid):
    """
    带障碍物的不同路径
    """
    if not obstacleGrid or not obstacleGrid[0]:
        return 0
    
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    
    # 起点或终点有障碍物
    if obstacleGrid[0][0] == 1 or obstacleGrid[m-1][n-1] == 1:
        return 0
    
    dp = [[0] * n for _ in range(m)]
    dp[0][0] = 1
    
    # 初始化第一列
    for i in range(1, m):
        if obstacleGrid[i][0] == 0:
            dp[i][0] = dp[i-1][0]
    
    # 初始化第一行
    for j in range(1, n):
        if obstacleGrid[0][j] == 0:
            dp[0][j] = dp[0][j-1]
    
    # 状态转移
    for i in range(1, m):
        for j in range(1, n):
            if obstacleGrid[i][j] == 0:
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
    
    return dp[m-1][n-1]

#### 空间优化版本
def uniquePathsWithObstacles_optimized(obstacleGrid):
    if not obstacleGrid or not obstacleGrid[0]:
        return 0
    
    m, n = len(obstacleGrid), len(obstacleGrid[0])
    
    if obstacleGrid[0][0] == 1:
        return 0
    
    dp = [0] * n
    dp[0] = 1
    
    for i in range(m):
        for j in range(n):
            if obstacleGrid[i][j] == 1:
                dp[j] = 0
            elif j > 0:
                dp[j] += dp[j-1]
    
    return dp[n-1]

4. 三维DP问题

4.1 三维最大路径和

问题描述:在三维网格中从起点到终点,求最大路径和。

python 复制代码
def maxPathSum3D(grid):
    """
    三维最大路径和
    grid: m x n x p 的三维列表
    """
    if not grid or not grid[0] or not grid[0][0]:
        return 0
    
    m, n, p = len(grid), len(grid[0]), len(grid[0][0])
    
    # 创建三维DP数组
    dp = [[[0] * p for _ in range(n)] for _ in range(m)]
    
    # 初始化起点
    dp[0][0][0] = grid[0][0][0]
    
    # 初始化三个面的边界
    for i in range(1, m):
        dp[i][0][0] = dp[i-1][0][0] + grid[i][0][0]
    
    for j in range(1, n):
        dp[0][j][0] = dp[0][j-1][0] + grid[0][j][0]
    
    for k in range(1, p):
        dp[0][0][k] = dp[0][0][k-1] + grid[0][0][k]
    
    # 初始化三个棱
    for i in range(1, m):
        for j in range(1, n):
            dp[i][j][0] = max(dp[i-1][j][0], dp[i][j-1][0]) + grid[i][j][0]
    
    for i in range(1, m):
        for k in range(1, p):
            dp[i][0][k] = max(dp[i-1][0][k], dp[i][0][k-1]) + grid[i][0][k]
    
    for j in range(1, n):
        for k in range(1, p):
            dp[0][j][k] = max(dp[0][j-1][k], dp[0][j][k-1]) + grid[0][j][k]
    
    # 状态转移
    for i in range(1, m):
        for j in range(1, n):
            for k in range(1, p):
                dp[i][j][k] = max(
                    dp[i-1][j][k],
                    dp[i][j-1][k],
                    dp[i][j][k-1]
                ) + grid[i][j][k]
    
    return dp[m-1][n-1][p-1]

#### 空间优化版本(滚动数组)
def maxPathSum3D_optimized(grid):
    m, n, p = len(grid), len(grid[0]), len(grid[0][0])
    
    # 使用二维数组滚动
    dp = [[0] * n for _ in range(p)]
    
    # 初始化
    dp[0][0] = grid[0][0][0]
    
    # 第一层初始化
    for j in range(1, n):
        dp[0][j] = dp[0][j-1] + grid[0][0][j]
    
    for k in range(1, p):
        dp[k][0] = dp[k-1][0] + grid[0][0][k]
    
    # 状态转移
    for i in range(1, m):
        new_dp = [[0] * n for _ in range(p)]
        
        # 更新第一行第一列
        new_dp[0][0] = dp[0][0] + grid[i][0][0]
        
        for j in range(1, n):
            new_dp[0][j] = max(dp[0][j], new_dp[0][j-1]) + grid[i][0][j]
        
        for k in range(1, p):
            new_dp[k][0] = max(dp[k][0], new_dp[k-1][0]) + grid[i][0][k]
        
        # 更新内部
        for j in range(1, n):
            for k in range(1, p):
                new_dp[k][j] = max(
                    dp[k][j],        # 从上到当前层
                    new_dp[k][j-1],  # 从左到当前层
                    new_dp[k-1][j]   # 从上层到当前层
                ) + grid[i][j][k]
        
        dp = new_dp
    
    return dp[p-1][n-1]

4.2 切披萨问题 (LeetCode 1444)

问题描述:将矩形披萨切成k块,每块至少有一个苹果,求切割方式数。

python 复制代码
def ways(pizza, k):
    """
    切披萨问题 - 三维DP
    """
    MOD = 10**9 + 7
    m, n = len(pizza), len(pizza[0])
    
    # 预处理苹果数量的前缀和
    # apples[i][j]: 从(i,j)到(m-1,n-1)的苹果数
    apples = [[0] * (n+1) for _ in range(m+1)]
    
    for i in range(m-1, -1, -1):
        for j in range(n-1, -1, -1):
            apples[i][j] = apples[i+1][j] + apples[i][j+1] - apples[i+1][j+1]
            if pizza[i][j] == 'A':
                apples[i][j] += 1
    
    # dp[i][j][c]: 从(i,j)开始,切成c块的方式数
    dp = [[[0] * (k+1) for _ in range(n)] for _ in range(m)]
    
    # 初始化:如果当前区域有苹果,可以切成1块
    for i in range(m):
        for j in range(n):
            if apples[i][j] > 0:
                dp[i][j][1] = 1
    
    # 状态转移
    for cut in range(2, k+1):
        for i in range(m):
            for j in range(n):
                # 水平切
                for ii in range(i+1, m):
                    if apples[i][j] > apples[ii][j]:  # 上面部分有苹果
                        dp[i][j][cut] = (dp[i][j][cut] + dp[ii][j][cut-1]) % MOD
                
                # 垂直切
                for jj in range(j+1, n):
                    if apples[i][j] > apples[i][jj]:  # 左边部分有苹果
                        dp[i][j][cut] = (dp[i][j][cut] + dp[i][jj][cut-1]) % MOD
    
    return dp[0][0][k] % MOD

5. 四维及以上DP问题

5.1 出界的路径数 (LeetCode 576)

问题描述:在网格中移动N步后出界的路径数。

python 复制代码
def findPaths(m, n, maxMove, startRow, startColumn):
    """
    出界的路径数 - 三维DP
    dp[move][i][j]: 移动move步后在(i,j)的路径数
    """
    MOD = 10**9 + 7
    
    # 方向数组
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    
    # 三维DP数组
    dp = [[[0] * n for _ in range(m)] for _ in range(maxMove + 1)]
    
    # 初始化:0步时在起点
    dp[0][startRow][startColumn] = 1
    
    result = 0
    
    for move in range(maxMove):
        for i in range(m):
            for j in range(n):
                count = dp[move][i][j]
                
                if count > 0:
                    for di, dj in directions:
                        ni, nj = i + di, j + dj
                        
                        if 0 <= ni < m and 0 <= nj < n:
                            # 还在网格内
                            dp[move + 1][ni][nj] = (dp[move + 1][ni][nj] + count) % MOD
                        else:
                            # 出界了
                            result = (result + count) % MOD
    
    return result % MOD

#### 空间优化版本(滚动数组)
def findPaths_optimized(m, n, maxMove, startRow, startColumn):
    MOD = 10**9 + 7
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    
    # 只使用两个二维数组
    dp_prev = [[0] * n for _ in range(m)]
    dp_prev[startRow][startColumn] = 1
    
    result = 0
    
    for move in range(maxMove):
        dp_curr = [[0] * n for _ in range(m)]
        
        for i in range(m):
            for j in range(n):
                count = dp_prev[i][j]
                
                if count > 0:
                    for di, dj in directions:
                        ni, nj = i + di, j + dj
                        
                        if 0 <= ni < m and 0 <= nj < n:
                            dp_curr[ni][nj] = (dp_curr[ni][nj] + count) % MOD
                        else:
                            result = (result + count) % MOD
        
        dp_prev = dp_curr
    
    return result % MOD

5.2 扔鸡蛋问题 (LeetCode 887)

问题描述:有k个鸡蛋,n层楼,找到鸡蛋恰好不会摔碎的楼层的最小尝试次数。

python 复制代码
def superEggDrop(k, n):
    """
    扔鸡蛋问题 - 二维DP
    dp[moves][eggs]: 用moves次移动,eggs个鸡蛋,最多能检查的楼层数
    """
    # dp[m][k] = dp[m-1][k-1] + dp[m-1][k] + 1
    # 含义:在第X层扔鸡蛋
    # 1. 鸡蛋碎了:可以检查 dp[m-1][k-1] 层
    # 2. 鸡蛋没碎:可以检查 dp[m-1][k] 层
    # 3. 加上当前层
    
    dp = [[0] * (k + 1) for _ in range(n + 1)]
    
    for moves in range(1, n + 1):
        for eggs in range(1, k + 1):
            dp[moves][eggs] = dp[moves-1][eggs-1] + dp[moves-1][eggs] + 1
            
            if dp[moves][eggs] >= n:
                return moves
    
    return n

#### 空间优化版本
def superEggDrop_optimized(k, n):
    """
    空间优化:使用一维数组
    """
    dp = [0] * (k + 1)  # dp[eggs]
    moves = 0
    
    while dp[k] < n:
        moves += 1
        # 从后往前更新,避免覆盖
        for eggs in range(k, 0, -1):
            dp[eggs] = dp[eggs] + dp[eggs-1] + 1
    
    return moves

#### 二分查找解法
def superEggDrop_binary(k, n):
    """
    数学解法:使用二分查找
    """
    def f(x):
        # 计算用x次移动,k个鸡蛋能检查的最大楼层数
        ans = 0
        r = 1
        for i in range(1, k + 1):
            r *= x - i + 1
            r //= i
            ans += r
            if ans >= n:
                break
        return ans
    
    lo, hi = 1, n
    while lo < hi:
        mid = (lo + hi) // 2
        if f(mid) < n:
            lo = mid + 1
        else:
            hi = mid
    
    return lo

6. 状态压缩DP

6.1 旅行商问题 (TSP)

问题描述:访问所有城市并回到起点的最短路径。

python 复制代码
def tsp(dist):
    """
    旅行商问题 - 状态压缩DP
    dist: n x n 距离矩阵
    """
    n = len(dist)
    
    # dp[mask][i]: 访问过mask中的城市,当前在城市i的最小代价
    # mask是二进制位掩码,第i位为1表示访问过城市i
    INF = float('inf')
    dp = [[INF] * n for _ in range(1 << n)]
    
    # 初始化:从任意城市开始
    for i in range(n):
        dp[1 << i][i] = 0
    
    # 状态转移
    for mask in range(1 << n):
        for i in range(n):
            if not (mask & (1 << i)):
                continue  # 当前城市不在mask中
            
            for j in range(n):
                if mask & (1 << j):
                    continue  # 城市j已经访问过
                
                new_mask = mask | (1 << j)
                dp[new_mask][j] = min(dp[new_mask][j], 
                                     dp[mask][i] + dist[i][j])
    
    # 回到起点
    answer = INF
    full_mask = (1 << n) - 1
    for i in range(n):
        answer = min(answer, dp[full_mask][i] + dist[i][0])
    
    return answer

#### 记忆化搜索版本
def tsp_memo(dist):
    n = len(dist)
    FULL_MASK = (1 << n) - 1
    
    @lru_cache(None)
    def dfs(mask, city):
        # 所有城市都访问过了,回到起点
        if mask == FULL_MASK:
            return dist[city][0]
        
        res = float('inf')
        for next_city in range(n):
            if not (mask & (1 << next_city)):
                new_mask = mask | (1 << next_city)
                cost = dist[city][next_city] + dfs(new_mask, next_city)
                res = min(res, cost)
        
        return res
    
    return dfs(1, 0)  # 从城市0开始,mask为1(只有城市0被访问)

6.2 矩阵中的最长递增路径 (LeetCode 329)

问题描述:在矩阵中找到最长递增路径的长度。

python 复制代码
def longestIncreasingPath(matrix):
    """
    矩阵中的最长递增路径 - 记忆化搜索
    """
    if not matrix or not matrix[0]:
        return 0
    
    m, n = len(matrix), len(matrix[0])
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    
    @lru_cache(None)
    def dfs(i, j):
        max_len = 1
        
        for di, dj in directions:
            ni, nj = i + di, j + dj
            
            if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] > matrix[i][j]:
                max_len = max(max_len, 1 + dfs(ni, nj))
        
        return max_len
    
    result = 0
    for i in range(m):
        for j in range(n):
            result = max(result, dfs(i, j))
    
    return result

#### DP+拓扑排序版本
def longestIncreasingPath_topo(matrix):
    if not matrix or not matrix[0]:
        return 0
    
    m, n = len(matrix), len(matrix[0])
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    
    # 计算每个点的出度(有多少个更大的邻居)
    outdegree = [[0] * n for _ in range(m)]
    
    for i in range(m):
        for j in range(n):
            for di, dj in directions:
                ni, nj = i + di, j + dj
                if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] > matrix[i][j]:
                    outdegree[i][j] += 1
    
    # 初始化队列:出度为0的点(局部最大值)
    queue = []
    for i in range(m):
        for j in range(n):
            if outdegree[i][j] == 0:
                queue.append((i, j))
    
    level = 0
    while queue:
        level += 1
        next_queue = []
        
        for i, j in queue:
            for di, dj in directions:
                ni, nj = i + di, j + dj
                
                # 指向当前点的邻居(值更小的点)
                if 0 <= ni < m and 0 <= nj < n and matrix[ni][nj] < matrix[i][j]:
                    outdegree[ni][nj] -= 1
                    if outdegree[ni][nj] == 0:
                        next_queue.append((ni, nj))
        
        queue = next_queue
    
    return level

7. 概率DP

7.1 骑士在棋盘上的概率 (LeetCode 688)

问题描述:骑士在n×n棋盘上随机移动k步后仍留在棋盘上的概率。

python 复制代码
def knightProbability(n, k, row, column):
    """
    骑士在棋盘上的概率 - 三维DP
    """
    # 骑士的8个移动方向
    directions = [
        (-2, -1), (-2, 1), (-1, -2), (-1, 2),
        (1, -2), (1, 2), (2, -1), (2, 1)
    ]
    
    # dp[step][i][j]: 移动step步后在(i,j)的概率
    dp_prev = [[0] * n for _ in range(n)]
    dp_prev[row][column] = 1.0
    
    for step in range(k):
        dp_curr = [[0] * n for _ in range(n)]
        
        for i in range(n):
            for j in range(n):
                prob = dp_prev[i][j]
                
                if prob > 0:
                    for di, dj in directions:
                        ni, nj = i + di, j + dj
                        
                        if 0 <= ni < n and 0 <= nj < n:
                            dp_curr[ni][nj] += prob / 8.0
        
        dp_prev = dp_curr
    
    # 计算总概率
    total_prob = 0.0
    for i in range(n):
        for j in range(n):
            total_prob += dp_prev[i][j]
    
    return total_prob

#### 空间优化版本
def knightProbability_optimized(n, k, row, column):
    directions = [
        (-2, -1), (-2, 1), (-1, -2), (-1, 2),
        (1, -2), (1, 2), (2, -1), (2, 1)
    ]
    
    # 使用两个二维数组
    dp = [[0] * n for _ in range(n)]
    dp[row][column] = 1.0
    
    for step in range(k):
        dp_next = [[0] * n for _ in range(n)]
        
        for r in range(n):
            for c in range(n):
                if dp[r][c] == 0:
                    continue
                
                for dr, dc in directions:
                    nr, nc = r + dr, c + dc
                    
                    if 0 <= nr < n and 0 <= nc < n:
                        dp_next[nr][nc] += dp[r][c] / 8.0
        
        dp = dp_next
    
    # 计算总概率
    total = 0.0
    for r in range(n):
        for c in range(n):
            total += dp[r][c]
    
    return total

8. 计数DP

8.1 不同子序列 (LeetCode 115)

问题描述:计算s中有多少个不同的子序列等于t。

python 复制代码
def numDistinct(s, t):
    """
    不同子序列 - 二维DP
    dp[i][j]: s前i个字符中,t前j个字符出现的次数
    """
    m, n = len(s), len(t)
    
    # dp[i][j]: s[0:i] 中有多少个不同的子序列等于 t[0:j]
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 初始化:空字符串是任何字符串的子序列
    for i in range(m + 1):
        dp[i][0] = 1
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s[i-1] == t[j-1]:
                # 可以选择匹配或不匹配
                dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
            else:
                # 只能不匹配
                dp[i][j] = dp[i-1][j]
    
    return dp[m][n]

#### 空间优化版本
def numDistinct_optimized(s, t):
    m, n = len(s), len(t)
    
    if n > m:
        return 0
    
    # 使用一维数组
    dp = [0] * (n + 1)
    dp[0] = 1  # 空字符串
    
    for i in range(1, m + 1):
        # 从后往前更新,避免覆盖
        for j in range(min(i, n), 0, -1):
            if s[i-1] == t[j-1]:
                dp[j] += dp[j-1]
    
    return dp[n]

9. 多维DP优化技巧

9.1 滚动数组优化

对于DP数组,如果当前状态只依赖于前几个状态,可以使用滚动数组减少空间复杂度。

python 复制代码
# 三维DP滚动数组示例
def dp_with_rolling_array(m, n, p):
    # 传统三维DP:O(m*n*p)空间
    dp = [[[0] * p for _ in range(n)] for _ in range(m)]
    
    # 滚动数组:O(n*p)空间
    dp_prev = [[0] * p for _ in range(n)]
    dp_curr = [[0] * p for _ in range(n)]
    
    for i in range(m):
        # 计算dp_curr
        for j in range(n):
            for k in range(p):
                dp_curr[j][k] = ...  # 根据dp_prev计算
        
        # 滚动
        dp_prev, dp_curr = dp_curr, dp_prev
    
    return ...

9.2 状态压缩优化

对于某些DP问题,可以使用位运算压缩状态。

python 复制代码
# 状态压缩示例:旅行商问题
def tsp_state_compression(dist):
    n = len(dist)
    FULL_MASK = (1 << n) - 1
    
    # dp[mask][i] 可以表示为字典或二维数组
    dp = [[float('inf')] * n for _ in range(1 << n)]
    
    # 或者使用字典存储有效状态
    dp_dict = {}
    for i in range(n):
        dp_dict[(1 << i, i)] = 0
    
    # 状态转移
    for mask in range(1 << n):
        for i in range(n):
            if not (mask & (1 << i)):
                continue
            
            for j in range(n):
                if mask & (1 << j):
                    continue
                
                new_mask = mask | (1 << j)
                dp[new_mask][j] = min(dp[new_mask][j], 
                                     dp[mask][i] + dist[i][j])
    
    return ...

9.3 记忆化搜索

对于复杂的状态转移,记忆化搜索通常比迭代DP更直观。

python 复制代码
from functools import lru_cache

def dp_with_memoization(params):
    @lru_cache(maxsize=None)
    def dfs(state1, state2, ...):
        # 终止条件
        if is_terminal(state1, state2, ...):
            return base_value
        
        # 状态转移
        result = initial_value
        for next_state in get_next_states(state1, state2, ...):
            result = combine(result, dfs(next_state) + cost)
        
        return result
    
    return dfs(initial_state)

10. 多维DP解题模板

10.1 通用解题步骤

  1. 分析问题维度

    • 确定需要几个维度表示状态
    • 每个维度的含义是什么
  2. 定义状态

    • 明确dp数组的含义
    • 确定状态变量的范围
  3. 推导转移方程

    • 分析状态之间的转移关系
    • 考虑所有可能的转移路径
  4. 初始化

    • 设置边界条件
    • 处理特殊情况
  5. 确定遍历顺序

    • 确保依赖状态已计算
    • 优化空间复杂度
  6. 计算结果

    • 从最终状态提取答案
    • 处理可能的溢出或边界

10.2 常见问题模式

问题类型 维度数 状态含义 优化技巧
网格路径 2 dp[i][j] 位置(i,j)的最优解 滚动数组
三维路径 3 dp[i][j][k] 三维位置 降维打击
状态压缩 2 dp[mask][i] 访问状态+当前位置 位运算
概率DP 3 dp[step][i][j] 步数+位置 矩阵快速幂
计数DP 2 dp[i][j] 前i个中前j个的计数 前缀和优化

10.3 复杂度分析

维度 状态数 时间复杂度 空间复杂度(优化前) 空间复杂度(优化后)
2D m×n O(m×n) O(m×n) O(min(m,n))
3D m×n×p O(m×n×p) O(m×n×p) O(n×p)
状态压缩 2^n×n O(n²×2^n) O(n×2^n) O(2^n)
概率DP k×m×n O(k×m×n) O(k×m×n) O(m×n)

10.4 调试技巧

  1. 可视化DP表
python 复制代码
def print_dp_2d(dp):
    for row in dp:
        print(' '.join(f'{x:4d}' for x in row))

def print_dp_3d(dp):
    for k in range(len(dp[0][0])):
        print(f"Layer {k}:")
        for i in range(len(dp)):
            print(' '.join(f'{dp[i][j][k]:4d}' for j in range(len(dp[0]))))
  1. 小规模测试
python 复制代码
def test_small_case():
    # 创建小规模测试数据
    grid = [[1,2,3],[
相关推荐
AlenTech2 小时前
197. 上升的温度 - 力扣(LeetCode)
算法·leetcode·职场和发展
橘颂TA3 小时前
【Linux 网络】TCP 拥塞控制与异常处理:从原理到实践的深度剖析
linux·运维·网络·tcp/ip·算法·职场和发展·结构与算法
tobias.b3 小时前
408真题解析-2010-9-数据结构-折半查找的比较次数
java·数据结构·算法·计算机考研·408真题解析
源代码•宸3 小时前
Leetcode—404. 左叶子之和【简单】
经验分享·后端·算法·leetcode·职场和发展·golang·dfs
WBluuue3 小时前
数据结构与算法:dp优化——优化尝试和状态设计
c++·算法·leetcode·动态规划
im_AMBER4 小时前
Leetcode 105 K 个一组翻转链表
数据结构·学习·算法·leetcode·链表
sin_hielo4 小时前
leetcode 1877
数据结构·算法·leetcode
睡不醒的kun4 小时前
定长滑动窗口-基础篇(2)
数据结构·c++·算法·leetcode·职场和发展·滑动窗口·定长滑动窗口
庄小焱4 小时前
【机器学习】——房屋销售价格预测实战
人工智能·算法·机器学习·预测模型