You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

n次抛硬币游戏获胜概率计算:优化暴力算法支持大n值

问题

硬币被抛掷n次:当连续抛出两次正面(Heads)时,玩家1(Alice)得1分;当抛出正面后接反面(Tails)时,玩家2(Bob)得1分。
例如,在序列"HTTTHHHT"中,Alice从"HHH"子串获得2分,Bob从"HT"子串获得2分,最终结果为平局。

我实现了一个暴力算法来计算各玩家获胜或平局的概率,但该算法无法处理大n值。由于n次抛掷共有2n种排列,当2n超出整数限制时程序会失效;即使能处理更大数值,计算大n值的耗时也极长。如何开发一种无需生成所有排列的更优算法?

以下是我当前的算法(假设正面为0,反面为1,Alice对应HH,Bob对应HT):

public class Probability {
    public static void main(String[] args) {
        int n = 5;
        int[] winners = Permutations(n);
        float pA = winners[0] / (float) (1 << n);
        float pB = winners[1] / (float) (1 << n);
        float pT = winners[2] / (float) (1 << n);

        System.out.printf("%d Tosses. P(Alice): %.3f. P(Bob): %.3f. P(Tie): %.3f\n", n, pA, pB, pT);
    }

    public static int[] Permutations(int n) {
        int amtPerm = 1 << n; // How many permutations to generate
        int[] perm = new int[n]; // Fills with all 0 by default
        int[] winners = {1, 0, 0}; // Alice, Bob, Tie (Counted the first permutation of all 0s as an Alice win)

        for(int i = 1; i < amtPerm; i++) {
            int j = n-1;
            while(true) {
                if(perm[j] == 0) {
                    perm[j++] = 1;
                    for(; j < n; j++) { // set all values after j to 0
                        perm[j] = 0;
                    }
                    break;
                } else {
                    j--;
                }
            }
            // Traverse the new permutation to determine a winner
            int alice = 0, bob = 0;
            for(int k = 1; k < n; k++) {
                if(perm[k-1] == 0 && perm[k] == 0) alice++;
                else if(perm[k-1] == 0 && perm[k] == 1) bob++;
            }
            if(alice > bob) winners[0] += 1;
            else if(bob > alice) winners[1] += 1;
            else winners[2] += 1;
        }

        return winners;
    }
}

该算法在小n值下表现正常,但无法支持大n值计算。比如输入n=1000时,会得到错误的结果:"P(Alice): 1.000, P(Bob): 0.000, P(Tie): 0.000"。

优化方案:动态规划法

暴力枚举所有排列的复杂度是O(2^n),完全无法处理n≥30的情况。我们可以用动态规划跟踪状态和得分差的概率,将复杂度降到O(n),轻松支持n=1000甚至更大的数值。

核心思路

我们只需要跟踪两个关键信息:

  1. 最后一次抛掷的结果(正面/反面)——下一次抛掷是否产生得分完全取决于上一次的结果
  2. 当前Alice与Bob的得分差(Alice得分 - Bob得分)——直接决定最终胜负

状态定义

定义dp[k][s][last]为抛掷k次后,得分差为s,最后一次抛掷结果为last(0=正面,1=反面)的概率:

  • k:已抛掷次数,范围1到n
  • s:得分差,范围从-(k-1)到+(k-1)(k次抛掷最多有k-1个连续对,每次最多改变1分)
  • last:0或1,代表最后一次抛掷结果

初始状态

当k=1时,没有连续对,得分差为0:

  • dp[1][0][0] = 0.5(第一次抛正面的概率)
  • dp[1][0][1] = 0.5(第一次抛反面的概率)

状态转移

对于k>1的情况,分两种情况处理:

  1. 上一次最后是正面(last=0):
    • 本次抛正面:出现HH,Alice得1分,得分差+1 → dp[k][s+1][0] += dp[k-1][s][0] * 0.5
    • 本次抛反面:出现HT,Bob得1分,得分差-1 → dp[k][s-1][1] += dp[k-1][s][0] * 0.5
  2. 上一次最后是反面(last=1):
    • 本次抛正面:出现TH,无得分,得分差不变 → dp[k][s][0] += dp[k-1][s][1] * 0.5
    • 本次抛反面:出现TT,无得分,得分差不变 → dp[k][s][1] += dp[k-1][s][1] * 0.5

空间优化

因为计算k次的状态只需要k-1次的状态,我们可以用两个字典(或数组)分别存储当前状态和上一状态,避免使用三维数组浪费空间。对于n=1000,得分差的范围是-999到999,总共1999个可能值,空间占用极小。

最终概率计算

遍历n次抛掷后的所有状态:

  • Alice获胜概率:所有s>0的dp[n][s][0] + dp[n][s][1]之和
  • Bob获胜概率:所有s<0的dp[n][s][0] + dp[n][s][1]之和
  • 平局概率:dp[n][0][0] + dp[n][0][1]

示例伪代码

def calculate_probabilities(n):
    # 初始化上一状态:k=1时,得分差0,last0和last1的概率都是0.5
    prev = {}
    prev[(0, 0)] = 0.5  # (得分差, last) -> 概率
    prev[(0, 1)] = 0.5

    for k in range(2, n+1):
        curr = {}
        for (s, last), prob in prev.items():
            if last == 0:
                # 抛正面,得分+1
                key = (s+1, 0)
                curr[key] = curr.get(key, 0) + prob * 0.5
                # 抛反面,得分-1
                key = (s-1, 1)
                curr[key] = curr.get(key, 0) + prob * 0.5
            else:
                # 抛正面,得分不变
                key = (s, 0)
                curr[key] = curr.get(key, 0) + prob * 0.5
                # 抛反面,得分不变
                key = (s, 1)
                curr[key] = curr.get(key, 0) + prob * 0.5
        prev = curr

    p_alice = 0.0
    p_bob = 0.0
    p_tie = 0.0
    for (s, _), prob in prev.items():
        if s > 0:
            p_alice += prob
        elif s < 0:
            p_bob += prob
        else:
            p_tie += prob
    return p_alice, p_bob, p_tie

这个方法无需生成任何排列,通过状态转移直接计算概率,时间和空间复杂度均为O(n),即使n=1000也能快速得到准确结果。

内容的提问来源于stack exchange,提问作者Peterfvn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 19:31:02