Skip to content

概率与期望 DP

动态规划 ⭐⭐⭐ 进阶 💧 低频

💡 核心要点

概率 DP 正向累积概率,期望 DP 逆向递推代价,两者方向相反是最核心的区别。


识别信号

  • 题目涉及随机事件、概率分布或期望值计算
  • 关键词出现:概率期望随机骰子均匀分布随机游走
  • 求某个事件发生的概率(如"棋子最终留在棋盘上的概率")
  • 求从初始状态到达目标状态的期望步数、期望代价或期望轮数
  • 状态之间的转移带有概率权重,而非确定性跳转

通用思考框架

概率 DP(正向推进)

表示到达状态 的概率,从初始状态出发,沿转移方向正向累积:

  • 初始状态概率为 1,其余为 0
  • 每一步将当前状态的概率按转移权重分配到后继状态
  • 最终答案是目标状态的概率之和

期望 DP(逆向推进)

表示从状态 出发到达目标的期望步数/代价,从目标状态出发,逆向向起始状态推:

  • 目标状态的期望代价为 0(已到达,无需额外代价)
  • 每个状态的期望 = 所有可能转移的(后继期望 + 本步代价)的概率加权和
  • 最终答案是初始状态的

方向对比总结

类型方向初始值转移含义
概率 DP正向(起点 → 终点)起点概率 = 1概率向后继状态扩散
期望 DP逆向(终点 → 起点)终点期望 = 0期望从后继状态向前累积

浮点数注意事项

  • 统一使用 double,避免用 float(精度不足)
  • 题目若要求误差在 以内,double 通常足够
  • 概率之和在每一步应等于 1,可用于调试验证

典型例题:骑士在棋盘上的概率 LeetCode 688

题目描述

的棋盘上,骑士从位置 出发,每次随机等概率选择 8 个马步方向之一移动,共移动 步。求骑士最终仍留在棋盘上的概率。

五步法分析

第一步:定义状态

= 骑士在第 步时位于格子 的概率。

第二步:状态转移

骑士从 出发,以均等概率 跳向每个合法方向

这是正向概率 DP:从当前状态将概率分发给后继状态。

第三步:初始条件

骑士初始以概率 1 位于起点

第四步:计算顺序

按步数从 逐步推进,每步遍历所有格子,将概率扩散到下一步的合法位置。

第五步:提取答案

步时,所有仍在棋盘内的格子概率之和即为答案:

Java 完整代码

java
class Solution {
    public double knightProbability(int n, int k, int row, int column) {
        // 马的 8 个跳跃方向
        int[][] dirs = {
            {-2, -1}, {-2, 1}, {-1, -2}, {-1, 2},
            { 2, -1}, { 2, 1}, { 1, -2}, { 1, 2}
        };

        // dp[i][j] 表示当前步骑士位于 (i, j) 的概率
        double[][] dp = new double[n][n];
        dp[row][column] = 1.0;

        for (int step = 0; step < k; step++) {
            double[][] next = new double[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (dp[i][j] == 0) continue; // 剪枝:概率为 0 无需扩散
                    for (int[] dir : dirs) {
                        int ni = i + dir[0];
                        int nj = j + dir[1];
                        if (ni >= 0 && ni < n && nj >= 0 && nj < n) {
                            // 正向概率转移:将概率的 1/8 分配给后继状态
                            next[ni][nj] += dp[i][j] / 8.0;
                        }
                        // 跳出棋盘的部分概率自然消失,无需处理
                    }
                }
            }
            dp = next;
        }

        // 累加所有格子的概率,即为留在棋盘上的总概率
        double result = 0.0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                result += dp[i][j];
            }
        }
        return result;
    }
}

复杂度分析:

  • 时间复杂度:
  • 空间复杂度:(滚动数组优化后只需两层)

延伸题目

题目链接类型关键思路
新 21 点LC 837概率 DP正向推进,用前缀和优化转移
分汤LC 808概率 DP状态为剩余汤量,大数据时概率趋近 1 可剪枝
抛掷硬币LC 1230概率 DP = 前 枚硬币恰好 枚正面的概率
24 点游戏LC 679期望 DP / 搜索枚举运算顺序,判断能否凑出目标值

常见陷阱与调试

1. 概率 DP 和期望 DP 推进方向搞反

概率 DP 必须正向推进(起点 → 终点),因为当前状态的概率依赖于前驱状态已经确定的概率。期望 DP 必须逆向推进(终点 → 起点),因为当前状态的期望依赖于后继状态的期望。方向搞反会导致依赖的值尚未计算,结果错误。

2. 浮点精度问题

使用 float 在多步累加后误差会超出题目要求,统一使用 double。若题目允许整数分数表示,也可用分子分母分别维护,但通常 double 已足够。

3. 概率之和不为 1(状态遗漏)

正常情况下,所有可达状态的概率之和应等于初始概率 1.0。如果中途发现概率之和小于 1,说明有部分概率"泄漏"到了非法状态(如本题跳出棋盘),这是正常现象;若发现概率之和大于 1,则说明转移公式有误,存在重复计数。

4. 期望 DP 中忘记加每步代价

期望 DP 的转移公式是 ,其中 通常为 1(走这一步本身的代价)。漏掉 +1 是高频错误,会导致期望值偏小。

5. 环状依赖(期望 DP 有环)

若状态转移图存在环(如随机游走可能回到当前状态),朴素递推无法直接使用,需建立方程组用高斯消元求解,或用迭代法(值迭代)逼近不动点。