题目描述
长度为n的线段有多少种切割方法,切割时两个为一组,一组中第一个数字必须为4,两个数字都必须为正整数,例如n=10有两种:
| 4 | 6 |
| 4 | 1 | 4 | 1 |
注意,在所有可能的结构中,消息的长度不可以为 0。也就是说,不能出现这样的消息:| 4 | 0 |。
当然,答案可能会很大,所以你需要给出答案模 998244353 之后的结果。
示例1
输入:10
返回值:2
题目分析
直观的解法可以模拟切割过程,遍历所有可能的切割情况,根据题意可知,切割的每个部分(一组两个数)至少为5,最大可以到线段的长度,当剩余的线段长度小于5时,不能再进行切割,只有剩余的线段长度刚好等于0,才算是一种方法;
解题思路
方法1:模拟压缩过程,深度遍历所有可能的切割情况
dfs的遍历过程:
int res = 0;
dfs(n){
if(n == 0){
// 因为最初线段长度一定大于0,所以在后面切割过程中n等于0,刚好切割完成
res++;
}
// 剩余的线段不足以维持一个组,返回
if(n<5) return 0;
for(int i=5;i<=n;i++){
// 先分出一部分从5开始到n,遍历剩余n-i的部分
dfs(n-i);
}
}直接dfs的过程,需要遍历很多重复的数字,每一层都要遍历n,但随着n的减少,每层遍历的次数也减少,时间复杂度约为,很容易超时,可以使用记忆化递归,将之前已经计算过的数字存储下来,减少遍历的次数。
方法2:找到状态转移方程,使用动态规划
1.定义 dp[i] 是线段长度为 i 时的切割方法数,初始化dp[0]为1;
2.根据方法1中的递归思路,可知:
①当 i 大于等于5时,dp[i] = dp[i-5] + dp[i-6] + ... dp[i-i](dp[0]) 等式①,
②当 i 大于5时, dp[i-1] = dp[i-6] + dp[i-7]...dp[0] 等式②;
将等式①减去等式②可得:dp[i] - dp[i-1] = dp[i-5];
最后得到状态转移方程: dp[i] = dp[i-1] + dp[i-5],只需要将i从6遍历到n,计算dp[n]的方法数。
代码实现
方法1:记忆化深度遍历所有可能的切割情况
int mod = 998244353;
HashMap<Integer, Integer> map;
public int messageCount (int N) {
map = new HashMap<>();
return dfs(N);
}
public int dfs(int N){
// 记忆化存储
if(map.containsKey(N)){
return map.get(N);
}
// 线段分割完成,直接完成
if(N == 0) return 1;
// 剩余线段长度小于5,不能分割
if(N < 5){
return 0;
}
// 记录长度为N的线段可以分割的方法种数
int res = 0;
// 从N中分出 长度为j (最少为5)的线段,剩余长度为 1 ~ N-j
for(int j=5;j<=N;j++){
// 继续递归 N-j 的部分
if(!map.containsKey(N-j)){
int add = dfs(N-j);
map.put(N-j, add);
}
res = (res + map.get(N-j))%mod;
}
return res;
}时间复杂度:,使用记忆化后,每个数字只需要遍历一次,在外层的循环中,可以对之前遍历的数据直接返回,遍历的深度最多为 n/5 (每次都要减5),总次数 n^2/5,当数据量过大时,使用该方***报栈溢出错误;
空间复杂度:,递归的深度最多为 n/5,且需要 n 的空间来记忆化存储数据,所以空间复杂度为
。
方法2:动态规划
public int messageCount (int N) {
if(N < 5) return 0;
int[] dp = new int[N+1];
int mod = 998244353;
// 初始化
dp[5] = 1;
for(int i=6;i<=N;i++){
// 状态转移方程 dp[i] = dp[i-1] + dp[i-5]
dp[i] = (dp[i-1] + dp[i-5]) % mod;
}
return dp[N];
}时间复杂度:,需要从6开始遍历到N,遍历的次数为 N-5,时间复杂度为
;
空间复杂度:,需要借助 N+1 空间的数组,空间复杂度为
。

京公网安备 11010502036488号