数字翻译陈字符串的可能性 | 豆包MarsCode AI刷题

124 阅读3分钟

image.png

问题分析

将一个数字翻译成字符串,类似于“解码”问题,根据翻译规则,每个数字可以对应一个字母,可能存在单独翻译和成对翻译的多种方式。因此问题的本质是动态规划问题,寻找从左到右分割数字的方法数量。

解题思路

  1. 单个字符翻译
  • 每个数字 0-9 都可以单独翻译。
  • 例如:12258 中的 1 可以翻译成 'b'
  1. 两个字符翻译
  • 每一对连续的数字,如果在 10-25 之间,可以一起翻译。
  • 例如:12258 中的 12 可以翻译成 'm'
  1. 动态规划公式
  • 定义 dp[i] 为前 i 个数字有多少种翻译方法。
  • 转移公式: [ dp[i] = dp[i-1] + dp[i-2] \text{ (如果 num[i-2] 和 num[i-1] 组成的数字在 [10, 25])} ]
  • 初始状态:
    • dp[0] = 1:空数字有 1 种翻译方法。
    • dp[1] = 1:第一个数字可以翻译为一个字符。
  1. 实现步骤
  • 转换数字为字符串,逐位检查是否可以单独翻译或成对翻译。
  • 使用数组或两个变量记录 dp 的值,节约空间。

代码实现

public class Main {
    public static int solution(int num) {
        // 将数字转换为字符串方便处理
        String numStr = String.valueOf(num);
        int n = numStr.length();

        // 特殊情况:只有一个数字
        if (n == 0) return 0;
        if (n == 1) return 1;

        // 定义 dp 数组
        int[] dp = new int[n + 1];
        dp[0] = 1; // 空字符串翻译方法为 1
        dp[1] = 1; // 第一个字符翻译方法为 1

        // 动态规划过程
        for (int i = 2; i <= n; i++) {
            // 单独翻译当前字符
            dp[i] = dp[i - 1];

            // 检查两个字符是否可以一起翻译
            int twoDigit = Integer.parseInt(numStr.substring(i - 2, i));
            if (twoDigit >= 10 && twoDigit <= 25) {
                dp[i] += dp[i - 2];
            }
        }

        // 最终结果存储在 dp[n]
        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(solution(12258) == 5);  // 5
        System.out.println(solution(1400112) == 6); // 6
        System.out.println(solution(2110101) == 10); // 10
        System.out.println(solution(25) == 2); // 2
        System.out.println(solution(1023) == 4); // 4
    }
}

代码详解

  1. 输入处理
  • 使用 String.valueOf(num) 将数字转换为字符串,方便逐位检查。
  1. 动态规划数组初始化
  • dp[0] = 1:空字符串有一种翻译方法。
  • dp[1] = 1:单个字符的数字只能翻译成一个字母。
  1. 动态转移
  • 单独翻译:当前数字总是可以单独翻译,继承 dp[i-1]
  • 成对翻译:如果连续两位数字满足条件(10 <= twoDigit <= 25),增加 dp[i-2]
  1. 返回结果
  • 最终结果为 dp[n],即整个字符串的翻译方法数量。

优化方案

空间优化

由于 dp[i] 只依赖于 dp[i-1]dp[i-2],可以将空间复杂度从 (O(n)) 降为 (O(1))。

public static int solution(int num) {
    String numStr = String.valueOf(num);
    int n = numStr.length();

    if (n == 0) return 0;
    if (n == 1) return 1;

    int prev2 = 1; // dp[i-2]
    int prev1 = 1; // dp[i-1]
    int current = 0;

    for (int i = 2; i <= n; i++) {
        current = prev1;
        int twoDigit = Integer.parseInt(numStr.substring(i - 2, i));
        if (twoDigit >= 10 && twoDigit <= 25) {
            current += prev2;
        }
        prev2 = prev1;
        prev1 = current;
    }

    return prev1;
}

测试结果

示例 1: num = 12258

  • 翻译方式:
    1. 'bccfi'
    2. 'bwfi'
    3. 'bczi'
    4. 'mcfi'
    5. 'mzi'
  • 输出:5

示例 2: num = 1400112

  • 翻译方式:
    • 'nibbba'
    • 'nibbbk'
    • 'nbibbba'
    • 'nbibbbk'
    • 'bbbba'
    • 'bbbbk'
  • 输出:6

示例 3: num = 2110101

  • 翻译方式共有 10 种。
  • 输出:10

时间和空间复杂度

  1. 时间复杂度:(O(n))
  • 遍历字符串每一位。
  1. 空间复杂度
  • 使用动态数组:(O(n))。
  • 空间优化后:(O(1))。

总结

  • 本题是经典的动态规划问题。
  • 使用状态转移公式简化问题,优化后的实现高效且易读。
  • 可以进一步扩展为变种,增加其他翻译规则或字符限制。