> 文章列表 > 剑指 Offer 46. 把数字翻译成字符串

剑指 Offer 46. 把数字翻译成字符串

剑指 Offer 46. 把数字翻译成字符串

剑指 Offer 46. 把数字翻译成字符串

难度:middle\\color{orange}{middle}middle

其实就是有条件的 青蛙跳格子 问题。


题目描述

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

提示:

  • 0<=num<2310 <= num < 2^{31}0<=num<231

算法

(动态规划) O(n)O(n)O(n)

翻译的过程可以分为两种情况:

  • 首先我们可以把每一位单独翻译
  • 然后我们考虑组合某些连续的两位

那么我们可以归纳出翻译的规则,字符串的第 iii 位置:

  • 可以单独作为一位来翻译
  • 如果第 i−1i−1i1 位和第 iii 位组成的数字在 1025 之间,可以把这两位连起来翻译。含有前导零的两位数不在题目规定的翻译规则中,比如 [1, 4, 02]这种翻译是不对的。

我们可以用 f(i)f(i)f(i) 表示以第 iii 位结尾的前缀串翻译的方案数。考虑第 iii 位单独翻译和与前一位连接起来再翻译对 f(i)f(i)f(i) 的贡献。单独翻译对 f(i)f(i)f(i) 的贡献为 f(i−1)f(i−1)f(i1);如果第 i−1i−1i1 位存在,并且第 i−1i−1i1 位和第 iii 位形成的数字 xxx 满足 10≤x≤2510≤x≤2510x25,那么就可以把第 i−1i−1i1 位和第 iii 位连起来一起翻译,对 f(i)f(i)f(i) 的贡献为 f(i−2)f(i−2)f(i2),否则为 000。我们可以列出这样的动态规划转移方程:

f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]f(i)=f(i1)+f(i2)[i10,10x25]

边界条件为 f[0]=1f[0] = 1f[0]=1,下文有解释。

剑指 Offer 46. 把数字翻译成字符串

复杂度分析

  • 时间复杂度O(n)O(n)O(n)

  • 空间复杂度 : O(n)O(n)O(n),字符串使用额外的空间。

C++ 代码

class Solution {
public:int translateNum(int num) {string s = to_string(num);int n = s.size();vector<int> f(n + 1);f[0] = 1;for (int i = 1; i <= n; i ++) {f[i] = f[i - 1];if (i > 1) {int t = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');// 为什么大于10, 因为含有前导零的两位数不在题目规定的翻译规则中,比如02、03等等if (t >= 10 && t <= 25) f[i] += f[i - 2];}}return f[n];}
};

Q:为什么一个数字都没有的方案数是 1

A:f[0] 代表翻译前 0 个数字的方法数,这样的状态定义其实是没有实际意义的,但是 f[0] 的值需要保证边界是对的,即 f[1]f[2] 是对的。比如说,翻译前 1 个数只有一种方法,将其单独翻译,即 f[1] = f[1 - 1] = 1。翻译前两个数,
如果第 1 个数和第 2 个数可以组合起来翻译,那么 f[2] = f[1] + f[0] = 2 ,否则只能单独翻译第 2 个数,即 f[2] = f[1] = 1。因此,在任何情况下 f[0]1 都可以保证 f[1]f[2] 是正确的,所以 f[0] 应该取 1