一天一大 leet (把数字翻译成字符串) 难度:中等 DAY-9

@[TOC](一天一大 leet (把数字翻译成字符串) 难度:中等 DAY-9)

20200609

img

题目(难度:中等):

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

示例

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

提示

  • 0 <= num < 231

抛砖引玉

img

2205
11+1->22+1->33
  • 无论如何只要输入的不是空默认都会有一种结果
  • 那我们先申明一个空的数组,用于存放对应指针指到当前字符串位置是存在的结果数
  • 从索引为 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/)
公号: 坑人的小书童
坑人的小书童

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪中。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其中的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录中包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
我的理解是,你想让我编写一段 Python 代码,实现一个算法,用于在给定的探险记录中找到发现新营地最多且索引最小的探险记录。 那么首先,我需要解析题目中的输入输出要求。 输入要求: - expeditions:一个字符串数组,表示小扣第一次到访的所有营地,和之后的每次探险记录(一个记录就是一个字符串,其中营地通过子串 -> 连接)。 输出要求: - 如果所有的探险记录都没有发现新的营地,则返回 -1; - 否则,返回发现新营地最多且索引最小的探险记录的索引。注:探险记录的索引从 0 开始计数。 接下来,我可以考虑如何编写代码来解决这个问题。 我的思路是,首先用一个集合记录所有已知的营地名称,然后遍历每个探险记录,依次将其中的营地名称加入到集合中,并判断是否有新的营地被发现。 如果某个探险记录中发现了新的营地,那么我就记录下发现了多少个新营地,以及这个探险记录的索引。最后,遍历所有的探险记录,找到发现新营地最多且索引最小的记录。 代码如下: ```python def find_new_camp(expeditions): known_camps = set() # 已知的营地集合 max_new_camps = 0 # 最多的新营地数量 max_new_camps_index = -1 # 最多新营地数量的探险记录的索引 for i in range(len(expeditions)): sub_camps = expeditions[i].split("->") # 提取探险记录中所有的营地名称 num_new_camps = 0 # 记录这个探险记录发现的新营地数量 for camp in sub_camps: if camp not in known_camps: # 如果这个营地是新的,就记录一下 known_camps.add(camp) num_new_camps += 1 if num_new_camps > max_new_camps: # 如果发现了更多的新营地,就更新最大值和索引 max_new_camps = num_new_camps max_new_camps_index = i return max_new_camps_index if max_new_camps > 0 else -1 ``` 我使用了两个变量 `max_new_camps` 和 `max_new_camps_index` 来记录已经遍历过的探险记录中,发现了最多的新营地数量,以及具有最多新营地数量的探险记录的索引。然后我遍历所有的探险记录,统计其中发现的新营地数量,如果大于当前的最大值,就更新 `max_new_camps` 和 `max_new_camps_index` 变量。最后,如果发现了新的营地,就返回 `max_new_camps_index`。如果所有的探险记录都没有发现新营地,就返回 -1。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值