**leetcode刷题_(DP,backtraking)_code1155(掷骰子)

CODE1155:Number of Dice Rolls With Target Sum

You have d dice, and each die has f faces numbered 1, 2, …, f.

Return the number of possible ways (out of fd total ways) modulo 10^9 + 7 to roll the dice so the sum of the face up numbers equals target.

Example 1:

Input: d = 1, f = 6, target = 3
Output: 1
Explanation:
You throw one die with 6 faces. There is only one way to get a sum of 3.
Example 2:

Input: d = 2, f = 6, target = 7
Output: 6
Explanation:
You throw two dice, each with 6 faces. There are 6 ways to get a sum of 7:
1+6, 2+5, 3+4, 4+3, 5+2, 6+1.
Example 3:

Input: d = 2, f = 5, target = 10
Output: 1
Explanation:
You throw two dice, each with 5 faces. There is only one way to get a sum of 10: 5+5.
Example 4:

Input: d = 1, f = 2, target = 3
Output: 0
Explanation:
You throw one die with 2 faces. There is no way to get a sum of 3.
Example 5:

Input: d = 30, f = 30, target = 500
Output: 222616187
Explanation:
The answer must be returned modulo 10^9 + 7.

解答:
第一想法:回溯。从第一骰子开始遍历,对应每一种情况,每一轮产生新的路径,如果达到d=1以及target<f(即此次可以得到目标),res+=1。
设置global res,每当到达时,res+=1,排除d=1target超出的情况。
代码:

class Solution:
    def numRollsToTarget(self, d: int, f: int, target: int) -> int:
        global res
        res = 0
        self.re(d, f, target)
        mod = 10**9 +7
        return res%mod

    def re(self, d, f, target):
        #print(d,f,target)
        if f >= target and d == 1 and target > 0:
            global res
            res += 1
            print(res)
        elif d > 1:
            for i in range(1, f + 1):
                if i + (d-1)*f>target and target-i>0:

                    self.re(d - 1, f, target - i)
        else:
            return

理论上是行得通的,但是对于大数目时,会超时。
因为对于每个路径最后都会产生n个分支。指数级的路径数导致TLE。

优化:
一般回溯能解决的问题都应该考虑下 DP的解法。
回溯的过程中,会重复很多操作,包括全排列以及各级的路径扩展。

观察发现(其实是看答案看出来的):
1 func(d,f,target) = sum(func(d-1,f,target-1)+…+func(d-1,f,target-f))
如果d=1 并且target在[1,f]的范围内时,值为1,其余特殊情况均为0(target超出该范围)
2 然后为了避免重复使用某一个func(每一层都会调用之前层的结果和),所以我们记录已经存在的结果(使用字典记录),如果在字典里面,则直接加value。如果不在,则进入dp得到结果相加,并存储该value进字典。
3 直接排除部分边界情况

代码:

import time


def singleton(func):
    def count(*args):
        a = time.time()
        res = func(*args)
        b = time.time()
        print('time:',b - a)
        return res

    return count
    
class Solution:
    @singleton
    def numRollsToTarget(self, d: int, f: int, target: int) -> int:
        if d*f<target:
            return 0
        elif f*f == target:
            return 1

        global mem
        mem = {}

        res = self.re(d, f, target)
        mod = 10**9 +7
        return res%mod

    def re(self, d, f, target):
        if d == 1 and target>0 and target<=f:
            return 1
        elif d>1:
            global mem
            res = 0
            for i in range(1,f+1):
                if (d-1,target-i) in mem:
                    res+=mem[(d-1,target-i)]
                else:
                    if target-i>0:
                        temp = self.re(d-1,f,target-i)
                        mem[(d-1,target-i)] = temp
                        res+=temp
                    else:
                        continue
            return res
        else:
            return 0
su = Solution()
res = su.numRollsToTarget(30,30,500)
print(res)

########################################
新增使用迭代:
代码:

class Solution:
    
    def numRollsToTarget(self, d: int, f: int, target: int) -> int:
        if target>d*f:
            return 0
        temp = [1 for i in range(f)]
        res = []
        x = 1
        while x<d:
            ind = 0
            while ind < (x+1)*f:
                count = 1
                t = 0
                while count <= f and ind - count>=0:
                    if ind -count < x*f:
                        t+=temp[ind-count]
                    count+=1
                res.append(t)
                ind+=1
            x+=1
            temp = res
            res = []
        
        return temp[target-1]%(10**9+7)

相比递归,此处直接从1开始迭代到d。不会涉及到重复计算。

#######################################
剑指offer相似题:

面试题60. n个骰子的点数

把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]

同样的解法:

class Solution:
    def twoSum(self, n: int):
        x = 1
        temp = [1,1,1,1,1,1]
        res = []
        while x<n:
            ind = 0
            while ind<6*(x+1):
                #print(ind,'###')
                count = 1
                t = 0
                while count<=6 and ind - count >=0:
                    #print(temp)
                    if ind-count <6*x:
                        #print(ind-count)
                        #print(ind-count)
                        t+=(temp[ind-count])
                    count+=1

                #print(ind,t)
                res.append(t)
                ind+=1
            x+=1
            temp = res
            print(temp)
            res = []

            #print(temp,res)


        return list(filter(lambda a: a!=0,map(lambda a:a/(6**n),temp)))

su = Solution()
res = su.twoSum(2)
print(res)

#################################
TIPS:
1 对于回溯可以解决的问题,考虑DP的情况。
2 尽量减少重复的操作(1 使用DP 2 使用记录方式记录已经存在的情况的结果 3 排除边界)!!!!!!!!!!!!
3 DP设置终止的情况(1 边界(target超过范围返回0),2 正常情况d=1时返回1)
4 recursion + 字典 = iteration (很多DP的情况里)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值