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的情况里)