【面试题】把数字翻译成字符串(动态规划+使用滚动变量优化空间效率+数学法进一步优化空间效率)

题目描述

在这里插入图片描述

解法一:常规动态规划

  • 状态定义:dp[i] 表示以第 i 位数字结尾的数字子串对应的翻译方案数
  • 状态转移方程:
    在这里插入图片描述
    在这里插入图片描述
  • 初始状态:dp[0] = 1, dp[1] =1,即 “无数字” 和 “第 1 位数字” 的翻译方法数量均为 1。
    在这里插入图片描述

Java

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num); // 为方便获取数字的各个位上的数,先将其转为字符串再进行遍历
        int[] dp = new int[s.length()+1]; // dp[i] 表示以第 i 位数字(Xi)结尾的数字前缀子串对应的翻译方案数,而下标是从0开始的,第一位数字对应dp[1], 因此dp[]长度多+1。
        dp[0] = 1; //若num前两位组合可以被翻译,则此时 dp[2] = dp[1] + dp[0],而dp[1]=1,故dp[0]=1;若不可以被翻译,则此时 dp[2] = dp[1],不受dp[0]取值的影响
        dp[1] = 1;
        for(int i =2; i<dp.length; i++) {
            String tmp = s.substring(i-2, i); // 通过字符串切片获取两位数字组合(10*Xi-1+Xi)
            if(tmp.compareTo("10") >= 0 && tmp.compareTo("25")<=0) // 通过对比字符串ASCII码判断该组合两位数对应的区间
                dp[i] = dp[i-1] + dp[i-2]; // 组成的两位数可以被翻译
            else
                dp[i] = dp[i-1]; // 组成的两位数不可被翻译
        }
        return dp[s.length()];
    }
}

下面给出 dp 数组长度和 nums 长度一致的写法

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int[] dp = new int[s.length()]; // dp[i]对应以num中第 i+1位为结尾的数字可翻译的数目
        dp[0] = 1; // 第一位数字对应的翻译结果只有一种
        for(int i=1; i<s.length(); i++) { // 从 i=1开始遍历,即从数字第二位开始遍历
            String tmp = s.substring(i-1, i+1); // 取num中第 i-1位和第 i位组成两位数
            if(tmp.compareTo("10")>=0 && tmp.compareTo("25")<=0) { // 判断组合数是否可以被翻译
                if(i==1) dp[i] = dp[i-1] + 1; // 针对 i=1特殊处理,因为此时 i-2会发生索引越界
                else dp[i] = dp[i-1] + dp[i-2];
            }
            else dp[i] = dp[i-1];
        }
        return dp[s.length()-1];
    }
}

解法二:动态规划+滚动变量实现空间效率优化

前面申请和 num 长度一样的 dp数组需要 O(n) 的时间,但其实每轮循环里的 dp[i] 只和 dp[i-1]、 dp[i-2]有关,再往前的 dp值已经不需要用到了,因此可用两个变量 a、b分别记录 dp[i]、dp[i-1],这两个变量交替前进即可,当两位数字的组合也可以被翻译时,再通过一个 变量 c 记录下一个 dp[i] = a+b, 否则下一个的 dp[i] = a。这样就可以省去 dp数组占用的 O(n) 额外空间。
同时,我们考虑从数字的末尾开始,从右至左翻译并计算翻译方案的个数。

class Solution {
    public int translateNum(int num) {
        String s = String.valueOf(num);
        int a = 1, b= 1, c; // 定义两个变量记录上一轮的 dp[i], dp[i-1]
        for(int i=s.length()-2; i>=0; i--) { // 从末尾往前遍历
            String tmp = s.substring(i, i+2);
            // 判断组合两位数是否可被翻译
            if(tmp.compareTo("10") >= 0 && tmp.compareTo("25")<= 0) 
                c = a+b; // 此轮dp[i] = 上一轮dp[i](即此轮dp[i-1]) + 上一轮dp[i-1](即此轮dp[i-2])
            else c = a; // 此轮dp[i] = 上一轮dp[i](即此轮dp[i-1])
            b = a; // 更新b, 指向本轮dp[i-1]
            a = c; // 更新a, 指向本轮dp[i]
        }
        return a;
    }
}

解法三:数字求余法,进一步优化空间效率

上述方法虽然已经节省了 dp 列表的空间占用,但将数字转为字符串 s 仍使用了 O(N) 大小的额外空间。
优化思路:利用求余运算 num % 10 和整除运算 num //10,可获取数字 num 的各位数字(获取顺序为个位、十位、百位…)。因此,可以通过该运算实现 从右至左 的遍历。

class Solution {
    public int translateNum(int num) {
        int a = 1, b = 1; // 滚动变量
        int x, y = num % 10; // y初始指向个位上的数字
        while(num != 0) {
            num /= 10; // 将数字右移一位,剔除个位上的数字
            x = num % 10; // x 初始指向十位上的数字
            int tmp = 10 * x + y; // 组合两位数学
            int c = (tmp >= 10 && tmp <= 25) ? a + b : a;
            b = a;
            a = c;
            y = x; 
        }
        return a;
    }
}

参考

https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/mian-shi-ti-46-ba-shu-zi-fan-yi-cheng-zi-fu-chua-6/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值