再谈斐波那契,把数字翻译成字符串

再谈斐波那契,把数字翻译成字符串

      • 题目描述
      • 算法分析
        • python代码

  学习的过程就是用记忆构建一个知识网的过程,网的密度决定了你容易遗忘的程度。如果担心遗忘,就努力把这个知识的网构建的更密一些。在讲算法设置的大致流程的时候,我通过斐波那契数列讲解了一下算法求解优化的大致过程。从斐波那契数列讲解算法设计的思路这篇文章写得还是蛮精彩的,感兴趣的可以回过去看看。
  本文将继续遵循那篇文章的讲解再来谈一个比较有趣的算法题目。

题目描述

  给定一个数字,我们按照如下规则把它翻译为字符串,0翻译成"a",1翻译成"b"…25翻译成"z",根据字符的字母顺序对应数字。给定一个数字串,可能会翻译成多个字符串。例如,12258有5种不同的翻译,分别是"bccfi",“bwfi”,“bczi”,“mcfi"和"mzi”。请编程实现一个函数,用来计算一个数字有多少种不同的翻译。

算法分析

  接触到这个题目,我们先来做分析。如果直接给你一个很长很长的数,那肯定都不会分析了。所以先想想如何把问题化简。以题目中给出的12258为例,第一个数字是1,显然是1的话就会存在两种情况,第一种情况,1单独翻译。第二种情况,因为1可以和后面的数字合并起来翻译,这个很容易理解,只要和后面的数字合并起来小于26,都可以在单独翻译的基础上,多一种合并翻译。我们先看1单独翻译,那么后面我们就要求2258的翻译情况,如果我们把1和后面的2合并翻译,那么后面就要求258的翻译情况。
  这个问题也可以从后往前分析,因为性质是一样的,但是我觉得从前往后更容易理解。
  这个时候我们把问题规模缩小了,问题还是求数字的翻译情况,类型没有变。这个过程很适合用递归来实现。此时我们总结一下,这个化简问题的过程。如果当前数字可以和后面的数字合并翻译,那我们就会产生两个递归分支,我们的总数目就是两个递归分支数目的和。如果不能合并翻译,那么总数目就直接是后面数字的翻译数目。
  但是我们不能光分析出递归就停止,因为递归的算法一定要有终止条件,显然这个题目中的终止条件就是翻译完了最后一个字符,也就是说两个数字翻译出一个字符,和一个数字翻译出一个字符。所以写代码的时候剩余两个数字就可以停止了,如果可以合并,返回2,如果不能合并翻译返回1。
  但是如果我们继续分析,就会发现我们第一步把问题转换成了求2258的翻译数目,如果进一步化简问题,我们就需要求258的翻译数目,这个问题好像就是我们第一次分解求的第二个问题,这就涉及到我上篇文章中所写的,就是递归带来重复计算的问题,我们可以通过添加备忘录,来解决重复计算的问题。如果我们不对题目有过高的要求,到这里已经可以停止了,毕竟递归写出来的代码简单,而且这样的话避免了重复计算,复杂度不会高。
  但是,根据我们之前学到的算法分析套路,我们不能就此收手,我们还可以对递归继续优化。我们之前已经说过终止条件,就是一个字符,那么显然这就是我们所能解决的基础问题,递归是自顶向下的算法,我们能否通过自底向上的算法,动态规划来解决递归栈的调用问题,我们从我们所能解决的基础问题入手,那就是一个字符(可能是一个数字,可能是两个数字)。显然这个时候就是逆向思维,我们扫描到第 i i i个数字( i > 2 i>2 i>2),它有可能无法和前面的数字合并,到这个数字为止,翻译数目就是前面数字的翻译数目 f ( i ) = f ( i − 1 ) f(i)=f(i-1) f(i)=f(i1)。但是如果可以和前面的数字合并,那么可以单独翻译,此时的翻译数目就是 f ( i ) f(i) f(i),可以合并翻译,翻译数目就是 f ( i − 2 ) f(i-2) f(i2),所以总的翻译数目就是 f ( i − 1 ) + f ( i − 2 ) f(i-1)+f(i-2) f(i1)+f(i2)
  我们可以对上面的分析总结一下,通过指示函数 I ( x ) ∈ 0 , 1 I(x)\in{0,1} I(x)0,1来表示第x个字符是否可以和前面的字符合并翻译,则我们轻易推出公式,截止第x个字符,可翻译的数目 f ( x ) = f ( x − 1 ) + I ( x ) f ( x − 2 ) , x > 2 f(x)=f(x-1)+I(x)f(x-2), x>2 f(x)=f(x1)+I(x)f(x2),x>2,通过这种分析,就很容易写出动态规划状态转移方程了。可能很多人写出这个转移方程才恍然大悟,这个怎么这么像斐波那契数列的递推式,这个问题比较简单,如果大家能轻易看出来这就是有条件的斐波那契数列,也可以直接写出代码

python代码

def getTranslationsCount(num):# 未对异常输入做出处理
    if isinstance(num,int):
        num=str(num)
    f0=0
    f1=1
    for i in range(len(num)):
        if num[i-1]=='1' or num[i-1]=='2' and num[i]<'6': # 判断是否可以和前面的数字合并
            f=f0+f1
        else:
            f=f1
        f0=f1
        f1=f
    return f

  这个代码的复杂度分析很简单了,当然最后的代码实现了空间复杂度 O ( 1 ) O(1) O(1),时间复杂度 O ( n ) O(n) O(n),至于其他分析的复杂度也在我的链接文章中详细讲解过,这里不重复赘述。
  这是一个很简单的问题,在这里的讲解主要是为了巩固文章开头链接的文章,同时也为了向大家展示一种斐波那契数列的变体问题,问题大家就当见过就行。问题分析的步骤大家应当了熟于心,对问题养成直觉之后便可脱离套路

你可能感兴趣的:(算法)