LeetCode/solutions/1155. Number of Dice Rolls With Target Sum.md
2020-02-16 14:19:36 +08:00

113 lines
3.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [1155. Number of Dice Rolls With Target Sum](https://leetcode.com/problems/number-of-dice-rolls-with-target-sum/)
# 思路
仔细分析一下可知d个骰子的和为 target 的情况数取决于 d-1 个骰子的和为target-1、target-2...target-f的情况数。所以这题是一个典型的动态规划题。状态定义为
```
dp[i][j]: i个骰子的和为j的情况数dp[0][0]=1
```
状态转移方程为:
```
dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] +...+ dp[i-1][j-f]
```
## 思路一、自上而下
动态规划一般都可以转换成带记忆数组的递归,这题也不例外。
时间复杂度O(d * f * target)空间复杂度O(d * target)
## 思路二、自下而上
更常见的是自下而上,而且我们还可以使用滚动数组对空间进行优化。
时间复杂度O(d * f * target)空间复杂度O(target)
## 思路三
思路二一共有三层循环,可以进一步优化时间。
回顾一下状态转移方程,
```
dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] +...+ dp[i-1][j-f]
j++后:
dp[i][j+1] = dp[i-1][j] + dp[i-1][j-1] +...+ dp[i-1][j-f+1]
```
所以说连续两次运行第三次循环是有大量重复的,所以第三层循环不是必须的。
可以这样考虑,`dp[i][j]` 的计算就是求一个大小为 f 的在数组`dp[i-1]`上的滑动窗口的元素和思路二的第三层循环每次都从头到尾累加窗口内的元素但事实上我们可以维护一个变量s代表窗口内元素的和当窗口向右滑动一步时我们只需要将s加上窗口右端元素然后减去刚刚离开窗口左端的元素即可。
时间复杂度O(d * target)空间复杂度O(target)
# C++
## 思路一
``` C++
class Solution {
private:
vector<vector<int>>dp = vector<vector<int>>(31, vector<int>(1001, -1));
public:
int numRollsToTarget(int d, int f, int target) {
if(target < 0 || target > d * f) return 0;
if(d == 0) return target == 0 ? 1 : 0;
if(dp[d][target] >= 0) return dp[d][target];
int res = 0;
for(int i = 1; i <=f; i++){
res += numRollsToTarget(d - 1, f, target - i);
res %= 1000000007;
}
dp[d][target] = res;
return res;
}
};
```
## 思路二
``` C++
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
if(target > d * f) return 0; // 会大大减少测试时间
vector<int>dp(target+1, 0);
dp[0] = 1;
for(int i = 1; i <= d; i++)
for(int j = target; j >= 0; j--){ // 注意这里要反向遍历!
int max_k = min(j, f); dp[j] = 0;
for(int k = 1; k <= max_k; k++){
dp[j] += dp[j-k];
dp[j] %= 1000000007;
}
}
return dp[target];
}
};
```
## 思路三
``` C++
class Solution {
public:
int numRollsToTarget(int d, int f, int target) {
const int M = 1000000007;
// dp表示dp[i][...], pre代表dp[i-1][...]
vector<int> dp(target + 1, 0), pre(target + 1, 0);
dp[0] = 1;
long long s = 0;
for(int i = 1; i <= d; i++){
for(int t = 0; t <= target; t++) pre[t] = dp[t];
s = 0;
for(int j = 0; j <= target; j++){
dp[j] = s;
s += pre[j]; // 累加上窗口右端元素
if(j >= f) s += M - pre[j - f]; // 减去刚刚离开窗口左端的元素
s %= M;
}
}
return dp[target];
}
};
```