[剑指-Offer] 60. n个骰子的点数(递归、动态规划、巧妙解法)

1. 题目来源

链接:n个骰子的点数
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

n 个骰子扔在地上,所有骰子朝上一面的点数之和为 s。输入 n,打印出 s 的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

  • 1 <= n <= 11

3. 题目解析

方法一:递归+剪枝+常规解法

你管这叫简单题???

扫描二维码关注公众号,回复: 9746416 查看本文章

n 个骰子,记住,这个字念 tou(一声)子… 的点数和的最小值为 n,最大值为 6n。那么所有点数的排列数就是 6 n 6^n 了。所有需要统计点数出现的次数再除以 6 n 6^n ,就能求出每个点数出现的概率了。

现在我们考虑如何统计每一个点数出现的次数。要想求出 n 个骰子的点数和,有以下思路:

  • 先把 n 个骰子分为两堆:第一堆只有一个,另一个有 n-1
  • 单独的一个可能出现从 1 到 6 的点数,需要计算从 1 到 6 的每一种点数和剩下的 n-1 个骰子来计算点数和
  • 接下来把剩下的 n-1 个骰子还是分成两堆,第一堆只有一个,第二堆有 n-2
  • 把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的 n-2 个骰子来计算点数和

分析到这里,我们不难发现这是一种递归的思路,递归结束的条件就是最后只剩下一个骰子。其本质也就是求数列 f ( n ) = f ( n 1 ) + f ( n 2 ) + f ( n 3 ) + f ( n 4 ) + f ( n 5 ) + f ( n 6 ) f(n)=f(n-1)+f(n-2)+f(n-3)+f(n-4)+f(n-5)+f(n-6)

至此,我们可以定义一个长度为 6n-n+1 的数组,因为和的范围就是 n~6n,和为 s 的点数出现的次数保存到数组的第 s - n 个元素里。

但是参考《剑指-Offer》的写法,会出现 TLE,最后一个样例过不去。其实可以考虑下怎么优化这个问题,使一维递归能够 AC。一开始从控制台单过 n=11 都过不了,想着优化,发现,这个概率是对称的,所以每次只需要算一半即可,经过修改的代码又出了点问题…n=1 过不了…然后看了看发现少了一组 6,直接把 1 的答案打表输出了…然后,提交,还是没有过,TLE 总的时间还是超过了,算了吧,至此了,挖个坑,回看的时候来填。

参见代码如下:

// 控制台单过11都过不了

class Solution {
public:
    vector<double> twoSum(int n) {
        int size = 6 * n;
        vector<double> v;
        vector<int> vt(size - n + 1, 0);
        help(n, vt);
        int total = pow(6, n);
        for (int i = n; i <= size; ++i) {
            double res = (double)vt[i - n] / total;
            v.push_back(res);
        }
        return v;
    }

    void help(int n, vector<int>& vt) {
        for (int i = 1; i <= 6; ++i) help(n, n, i, vt);
    }

    void help(int num, int tmp, int sum, vector<int>& vt) {
        if (tmp == 1) vt[sum - num]++;
        else {
            for (int i = 1; i <= 6; ++i)
                help(num, tmp - 1, i + sum, vt);
        }
    }
};

// 控制台能过11,总时间超TLE
class Solution {
public:
    vector<double> twoSum(int n) {
        if (n == 1) return {0.16667,0.16667,0.16667,0.16667,0.16667,0.16667};
        int size = 6 * n;
        vector<double> v;
        vector<int> vt(size - n + 1, 0);
        help(n, vt);
        int total = pow(6, n);
        for (int i = n; i < size / 2 + 6; ++i) {
            double res = (double)vt[i - n] / total;
            v.push_back(res);
        }
        for (int i = size / 2 - 6; i >= 0; --i) v.push_back(v[i]);
        return v;
    }

    void help(int n, vector<int>& vt) {
        for (int i = 1; i <= 6; ++i) help(n, n, i, vt);
    }

    void help(int num, int tmp, int sum, vector<int>& vt) {
        if (tmp == 1) vt[sum - num]++;
        else {
            for (int i = 1; i <= 6; ++i)
                help(num, tmp - 1, i + sum, vt);
        }
    }
};

方法二:动态规划+迭代+巧妙解法

d p [ i ] [ j ] dp[i][j] 表示当 n = i n=i 时,和为 j j 出现的排列情况总数

状态转移方程: d p [ i ] [ j ] = d p [ i 1 ] [ j 1 ] + d p [ i 1 ] [ j 2 ] + d p [ i 1 ] [ j 3 ] + d p [ i 1 ] [ j 4 ] + d p [ i 1 ] [ j 5 ] + d p [ i 1 ] [ j 6 ] dp[i][j]=dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6]

初始条件: d p [ 1 ] [ 1 ] = d p [ 1 ] [ 2 ] = d p [ 1 ] [ 3 ] = d p [ 1 ] [ 4 ] = d p [ 1 ] [ 5 ] = d p [ 1 ] [ 6 ] = 1 dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=dp[1][5]=dp[1][6]=1

参见代码如下:

// 执行用时 :0 ms, 在所有 C++ 提交中击败了100.00%的用户
// 内存消耗 :8 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
	vector<double> twoSum(int n) {
		vector<vector<int>> dp(n + 1, vector<int>(6 * n + 1, 0));
		double num = pow(6, n);
		vector<double> res(5 * n + 1, 1 / (double)6);
		//初始状态
		for (int i = 1; i <= 6; i++) dp[1][i] = 1;
         //从2到n计算dp
		for (int i = 2; i <= n; i++) {     
            //表示当n=i时候的点数和取值从i到6i
			for (int j = i; j <= i * 6; j++) {  
                //dp[i][j]=dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]+dp[i-1][j-5]+dp[i-1][j-6];
				for (int k = 1; k <= 6; k++) {  
                    //第i个骰子点数一定比i-1个骰子点数大
					if (j - k > 0) dp[i][j] += dp[i - 1][j - k]; 
					if (i == n) res[j - i] = dp[i][j] / num;
				}
			}
		}
		return res;
	}
};
发布了343 篇原创文章 · 获赞 197 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104786440