划分数字的方案数
难度:
标签:
题目描述
代码结果
运行时间: 2113 ms, 内存: 538.0 MB
/*
* 思路:
* 1. 通过递归和动态规划来计算字符串 num 的拆分方式。
* 2. 使用 Java Stream API 优化部分操作。
*/
import java.util.*;
import java.util.stream.IntStream;
public class Solution {
private static final int MOD = 1000000007;
public int numDecodings(String num) {
int n = num.length();
int[] dp = new int[n + 1];
dp[0] = 1;
IntStream.rangeClosed(1, n).forEach(i ->
IntStream.rangeClosed(1, i).mapToObj(j -> num.substring(j - 1, i))
.filter(this::isValid)
.forEach(sub -> dp[i] = (dp[i] + dp[i - sub.length()]) % MOD)
);
return dp[n];
}
private boolean isValid(String s) {
if (s.charAt(0) == '0') return false;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) < s.charAt(i - 1)) return false;
}
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.numDecodings("327")); // 输出:2
System.out.println(solution.numDecodings("094")); // 输出:0
System.out.println(solution.numDecodings("0")); // 输出:0
System.out.println(solution.numDecodings("9999999999999")); // 输出:101
}
}
解释
方法:
此题使用动态规划的方法。首先进行特殊情况的判断:如果字符串的第一个字符为'0',由于不能有前导0,直接返回0。定义dp数组f[i][j],表示考虑num的子串num[0..j],且最后一个数字为num[i..j]时的方案数。初始化f[0][i]=1,即当第一个数字扩展到i时,只有一种划分方案。对于每个位置i到j,若num[i]=='0',则跳过,因为不能有前导0。为了保证非递减,需要比较当前数字与前一个数字的大小。这通过预处理一个最长公共前缀数组lcp来辅助,减少比较次数。动态规划递推时,若当前数字大于等于前一个数字,则累加前面的方案数。最后,将所有以n-1结尾的方案数相加,得到总方案数。
时间复杂度:
O(n^2)
空间复杂度:
O(n^2)
代码细节讲解
🦆
动态规划数组f[i][j]表示什么意义,为什么它的初始值f[0][i]设为1?
▷🦆
如何理解和计算最长公共前缀数组lcp[i][j],它在算法中扮演什么角色?
▷🦆
在dp递推过程中,为何需要判断`num[i] == '0'`然后跳过某些步骤?
▷🦆
为什么在计算dp值时,需要比较`lcp[i - length][i]`与`length`,这个比较的逻辑是基于什么?
▷