@[TOC](一天一大 leet (把数字翻译成字符串) 难度:中等 DAY-9)
20200609
题目(难度:中等):
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示
- 0 <= num < 231
抛砖引玉
2 | 2 | 0 | 5 |
---|---|---|---|
1 | 1+1->2 | 2+1->3 | 3 |
- 无论如何只要输入的不是空默认都会有一种结果
- 那我们先申明一个空的数组,用于存放对应指针指到当前字符串位置是存在的结果数
- 从索引为 1 即第二个字母开始遍历,如果这个字母和之前的字母组合起来满足:
- 大于等于 10,小于等于 25,则当前当指针指到下一个字母时结果数就是前两次指针出现的结果数之和
- 不然当前当指针指到下一个字母时结果数保持不变
- 一个组合中添加一个新的元素,那能出现的新的组合是原组合数,
- 如果新添加的组合可以合并到原组合的最后一项,那么新组合应该是原组合数+原组合前一个组合数
- 最后输入字符串最后一个指针记录的结果数
/**
* @param {number} num
* @return {number}
*/
var translateNum = function (num) {
var Arr = String(num).split('')
var _result = []
for (var i = 1; i < Arr.length; i++) {
var itemNum = _result[i] || 1
// 前一个字母为0其实数据不变 需要忽略
if (Number(Arr[i - 1]) && Number(Arr[i - 1] + '' + Arr[i]) <= 25) {
_result[i + 1] = itemNum + (_result[i - 1] || 1)
} else {
_result[i + 1] = itemNum
}
}
return num.length ? _result[Arr.length] || 1 : 0
}
官方答案
动态规划
例如:输入 1402
- 每一位单独翻译,即 [1, 4, 0, 2][1,4,0,2],翻译的结果是 beac
- 然后我们考虑组合某些连续的两位:
- [14,0,2],翻译的结果是 oac。
- [1, 40, 2][1,40,2],这种情况是不合法的,因为 4040 不能翻译成任何字母。
- [1, 4, 02][1,4,02],这种情况也是不合法的,含有前导零的两位数不在题目规定的翻译规则中,那么 [14, 02][14,02] 显然也是不合法的。
- 那么我们可以归纳出翻译的规则,字符串的第 ii 位置:
- 可以单独作为一位来翻译
- 如果第 i−1 位和第 i 位组成的数字在 10 到 25 之间,可以把这两位连起来翻译
我们可以用)f(i) 表示以第 i 位结尾的前缀串翻译的方案数,考虑第 i 位单独翻译和与前一位连接起来再翻译对 f(i) 的贡献。
单独翻译对 f(i) 的贡献为 f(i−1);如果第 i−1 位存在,并且第 i−1 位和第 i 位形成的数字 x 满足 10≤x≤25,
那么就可以把第 i−1 位和第 i 位连起来一起翻译,对 f(i) 的贡献为 f(i−2),否则为 0。我们可以列出这样的动态规划转移方程:
f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]
边界条件是 f(−1)=0,f(0)=1。方程中 [c] 的意思是 c 为真的时候 [c]=1,否则 [c]=0。
有了这个方程我们不难给出一个时间复杂度为 O(n),空间复杂度为 O(n) 的实现。
考虑优化空间复杂度:这里的 f(i) 只和它的前两项 f(i−1) 和 f(i−2) 相关,我们可以运用「滚动数组」思想把 f 数组压缩成三个变量,这样空间复杂度就变成了 O(1)。
/**
* @param {number} num
* @return {number}
*/
var translateNum = function (num) {
var p = 0,
q = 0,
r = 1
for (var i = 0; i < String(num).length; ++i) {
p = q
q = r
r = 0
r += q
if (i == 0) {
continue
}
var pre = String(num).substring(i - 1, i + 1)
if (Number(pre) <= 25 && Number(pre) > 10) {
r += p
}
}
return r
}
高手在民间
- 使用取余得到需要判断的范围
- 范围满足大于 10 小于等于 25 就结果为前两次结果之和
- 不然几个与上一次一样
- 纯用除和取余就得到了结果,脑回路简直了 !!!∑(゚ Д ゚ノ)ノ
/**
* @param {number} num
* @return {number}
*/
var translateNum = function (num) {
if (num < 10) return 1
return num % 100 < 10 || num % 100 > 25
? translateNum(parseInt(num / 10))
: translateNum(parseInt(num / 10)) + translateNum(num / 100)
}
-
按照题目要求,输出一共有多少种翻译方式。
举几个简单例子:
9 只能翻译成 j,12 能翻译成 bc 和 m,30 只能翻译成 da。
这是因为 12 可以分开翻译也可以合起来翻译,而 30 必须分开翻译。
无论多长的数字,其翻译的多样性都是由这样的两个相邻数字的“分开翻译”与“组合翻译”造成的。 -
为了继续说明问题,以 123456 为例,可以有两种拆分:
- 一种是 1 23456,一种是 12 3456。对于第一种拆分,显然一个单独的 1 只能翻译成 b,所以 1 23456 的翻译方法数其实和 23456 没有区别。
- 对于第二种拆分,12 能合起来翻译,所以 12 3456 和 3456 的翻译方法数是没有区别的。
自然而然,我们举例描述了终止情况和递推关系,可以想到用递归方式。
- 递推关系:数字 abc…的翻译方法数=bc…的翻译方法数+(如果 ab 可组合翻译)c…的翻译方法数。
- 终止情况:
如果只剩 1 位数字,则必然只有一种翻译方法;
如果只剩 2 位数字,且组合起来小于 26,则有分开与组合两种翻译方式;
如果只剩 2 位数字且组合起来大于 25,则只有分开翻译一种翻译方法。
/**
* @param {number} num
* @return {number}
*/
var translateNum = function (num) {
if (num < 10) return 1
else if (num < 26) return 2
else if (num < 100) return 1
else {
var ans = 0
var a = 1
while (num / 10 >= a) a *= 10
ans = ans + translateNum(num % a)
a /= 10
if (num / a < 26) ans = ans + translateNum(num % a)
return ans
}
}
博客: 小书童博客(http://gaowenju.com/)
公号: 坑人的小书童