借用AI工具分析题目
从给出的分析结果发现这题目可以使用动态规划进行解答
动态规划思想解析
动态规划的核心是通过子问题的解来构造更大问题的解。在这个问题中:
- 状态定义:
-
- 定义
dp[i]为数字字符串前i位的翻译方法总数。 - 通过递推关系,可以逐步构建
dp[i]的值。
- 定义
- 状态转移方程:
-
- 如果只考虑当前位置的单个数字翻译,那么
dp[i] = dp[i-1]。 - 如果考虑当前位置和前一个位置的数字可以组成一个有效翻译(值在
[10, 25]范围内),那么还可以将dp[i-2]的贡献加上,即:
- 总体关系为:
- 如果只考虑当前位置的单个数字翻译,那么
- 初始条件:
-
dp[0] = 1:空字符串的翻译方法只有一种(没有字符)。dp[1] = 1:一个字符的翻译方法只有一种。
解释
String str = String.valueOf(num);
int n = str.length();
int[] dp = new int[n + 1];
- 将数字
num转换为字符串str,便于操作数字的每一位。 dp数组的长度为n+1,其中dp[i]表示前i个字符的翻译方法数。
dp[0] = 1;
dp[1] = 1;
- 初始化状态:空字符串和单字符字符串的翻译方法。
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1]; // 默认情况下可以单独翻译第i个字符
int twoDigit = Integer.parseInt(str.substring(i - 2, i)); // 取出最后两个字符
if (twoDigit >= 10 && twoDigit <= 25) {
dp[i] += dp[i - 2]; // 如果两位字符合法,加上它们的翻译方法
}
}
- 遍历字符串的每一位,从第2位开始,逐步计算
dp[i]。 dp[i-1]表示当前字符单独翻译的贡献。- 检查
twoDigit(由当前字符和前一个字符组成的两位数)是否在[10, 25]范围内。如果是,则加上dp[i-2]的贡献,因为这两位数字可以作为一个整体翻译。
return dp[n];
- 最终的翻译方法数为
dp[n],即整个字符串的翻译方法数。
示例分析
以 12258 为例:
- 数字对应的字符串为
12258,其长度为n=5。 - 初始化
dp数组:dp[0]=1,dp[1]=1。 - 从第2位开始迭代:
-
i=2:dp[2] = dp[1] + dp[0](因为12在[10, 25]范围内)。
-
-
dp[2] = 1 + 1 = 2
-
-
i=3:dp[3] = dp[2](因为22在[10, 25]范围内)。
-
-
dp[3] = dp[2] + dp[1] = 2 + 1 = 3
-
-
i=4:dp[4] = dp[3] + dp[2](因为25在[10, 25]范围内)。
-
-
dp[4] = 3 + 2 = 5
-
-
i=5:dp[5] = dp[4](因为58不在[10, 25]范围内)。
-
-
dp[5] = dp[4] = 5
-
- 最终
dp[5] = 5,对应的翻译方法有:bccfi,bwfi,bczi,mcfi,mzi。
为什么这样写?
- 动态规划高效解法:
-
- 通过定义状态和递推关系,避免重复计算子问题,降低复杂度。
- 时间复杂度:(O(n)),仅需一次遍历。
- 空间复杂度:(O(n)),仅需一个数组存储状态。
- 逻辑清晰,易于理解:
-
- 每一步的计算都基于明确的状态转移关系。
- 初始化简单,递推关系符合问题描述。
优化建议
可以将 dp 数组优化为两个变量,因为当前状态仅依赖于前两个状态:
public static int solution(int num) {
String str = String.valueOf(num);
int n = str.length();
int prev2 = 1, prev1 = 1;
for (int i = 2; i <= n; i++) {
int current = prev1;
int twoDigit = Integer.parseInt(str.substring(i - 2, i));
if (twoDigit >= 10 && twoDigit <= 25) {
current += prev2;
}
prev2 = prev1;
prev1 = current;
}
return prev1;
}
- 时间复杂度仍为 (O(n)),但空间复杂度降为 (O(1))。
唉还是感觉好难,还得继续练习dp类的题目