之前面试碰到一个算法题,自己实现了一版但是效率堪忧,求优化建议[已解决]
题目大概是这样的: 给你一个骰子(6面),可以无限制的投掷,每次投掷点数之和为总点数。
现问,投掷出总点数为20,共有多少种骰子组合。
投掷20次1点算是一种,投18次1点,再投一个2点又算一种。
以下是我的解法,用的递归方式,思路是用树的终端节点到根节点的和为总点数,然后计算有多少个终端节点。
验证了下 总数在,1、2、3、4都是对的,后面的太多没法验证。
现在的缺陷,当数值为50或者比较大的时候,就半天解不出来。
力扣上搜了下相关的题,答案区都是推荐用动态规划解,自己还是没思路,求解。
<?php
/**
* 总点数 total
* 当前总点数 cur_total
* 如果 当前总点数大于 总点数 就退回
*/
class Touzi
{
public $total = 20;
public $num = 0;
function next($num = 0, $cur_total = 0)
{
$cur_total = $cur_total + $num;
if ($cur_total > $this->total) {
return true;
} elseif ($cur_total == $this->total) {
$this->num = $this->num + 1;
return true;
}
for ($i = 1; $i <= 6; $i++) {
if ($this->next($i, $cur_total)) {
break;
};
}
return false;
}
}
$touzi = new Touzi();
$touzi->total = 6;
$touzi->next();
echo "组合数:" . $touzi->num;
你这个写法算是暴力破解,是有改进空间的,你的思路是正确的,用了树的结构,但是你可以思考下,这棵树是不是有相同的路径,这些相同的路径是不是可以“剪枝”?比如 1+19 和 19 +1 是一样的,但是在树中就重复了,你可以使用一个“备忘录”来记录已存在的路径,这样就大大减少了重复。
力扣里面有一个爬楼梯的算法,跟这个很像,每次可以走1步或者2步,然后有45层楼梯怎么爬?
@QJAutumn 对 感觉能用动态规划做出来,但是这个题分析不出来 :joy:
你这个算法错了
直接找出通项公式?感觉和之前做的一道题很像,就通过掷6面骰子,有多少种可能到达距离起始点N格的地方:
这个和斐波那契类似吧。
f(20) = f(19) + f(18) + f(17) + f(16) + f(15) + f(14)
可以先算出 f(1) 至 f(6) ,再求更大的就行了吧!
@鲜橙多 如果不同的顺序也算的话,那就是类似斐波那契数列的那种,可以自底向上来分析。
如果要凑出1,只有一种情况,掷骰子1点;
如果凑出2,就是两种情况,1+1和2;
如果凑出3,就是 1+1+1 、 1+2 和 2+1 和 3 四种情况,有没有发现,凑 3 的时候,用到了 凑2时候的组合,所以计算3的时候,不需要再从1开始算,直接使用刚才算2的时候的情况,也就是: 当第一次为1,剩余3-1=2,这个2刚才计算过了,2种情况,那么第一次为1的时候有两种,第一次为2的时候,剩余 3-2=1,1刚才也计算过了,只有一种情况,还有第一次掷出3,那么得出3就有 2+1+1 三种,4也是同理,首次为1,4-1=3,有4种,首次为2,4-2=2,有2种,首次为3,4-3=1,有一种,首次为4又一种,得出4就有 3+2+1+1=7种情况
那么往后的每种情况,都可以先分析分为 第一次为1,、第一次为2、第一次为3。。。。依次类推一直到第一次为6,那么就可以写出函数:
不过这么写还是有很多重复计算,所以可以新引入一个备忘录,来记录已经计算过的值,也就是我们常说的“空间换时间”
这样的话时间复杂度直接降为O(n)
不知道顺序有没有关系,比如你说的 18个1点和1个2点, 投掷 2 点在中间,是算1种?还是算多种呢?
20点,最多投掷 20次, 每次最小点数 1 点
20点,最少投掷 4次,63+21=20 共 4次
然后把 4次到 20 次的次数算出来 相加,
就是不知道有没有更好的
根据楼上大佬 @如梦又似幻 的思路分析出了以下公式
代码实现
附上同事用动态规划的解法 :+1: 太强了
动态规划的版本也写出来了,根据上面第二个公式。 :smile:
你这个顺序不同的需要去重吗?