【算法与数据结构】—— 动态规划之斐波那契问题

动态规划之斐波那契问题


斐波那契数列:

在数学上,斐波纳契数列以如下递归方式被定义:

F ( 0 ) = 1 , F ( 1 ) = 1 , … , F ( n ) = F ( n − 1 ) + F ( n − 2 ) ( n ≥ 2 , n ∈ N + ) F(0)=1, F(1)=1, … , F(n)=F(n-1)+F(n-2) \quad (n \ge 2, n \in N^+) F(0)=1,F(1)=1,,F(n)=F(n1)+F(n2)(n2,nN+)

根据定义,其前十项为 1, 1, 2, 3, 5, 8, 13, 21, 34, 55。

假设现在给出一个编程问题:输入一个自然数 n n n,要求输出斐波那契数列的第 n n n ( 0 ≤ n ≤ 1000000 ) (0≤n≤1000000) (0n1000000)。如果我们根据定义来求解本题,那么可以直接写出以下代码:

int Fibonacci(int n)
{
	if(n==0 || n==1) return 1;
	else return Fibonacci(n-1)+Fibonacci(n-2);
}

假设现在输入 n = 7 n=7 n=7,那么该程序的执行过程如下图所示:

求解Feibonacci(7)

上图表明:

  • 要计算Fibonacci(7),就要先算出Fibonacci(6)和Fibonacci(5);
  • 要计算Fibonacci(6),就要先算出Fibonacci(5)和Fibonacci(4);
  • 要计算Fibonacci(5),就要先算出Fibonacci(4)和Fibonacci(3);
  • ……

直到最终迭代到了 n = 1 n=1 n=1 0 0 0

而在这上面的计算中,我们发现其有大量的重复计算,比如红框部分。出现这样的现象的原因是,我们对于前面计算出的结果并没有将其记录下来,从而导致了重复计算。为了解决这个问题,我们可直接在递归的过程中,将所有结果都保存下来,改进后的代码如下

const int N=100000;
int fibonacci[N];
int Fibonacci(int n)
{
	fibonacci[0]=fibonacci[1]=1;
	for(int i=2;i<=n;i++)
		fibonacci[i]=fibonacci[i-1]+fibonacci[i-2];
	return fibonacci[n];
}

可以看出,改进后的算法其时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)。还能不能继续优化呢?答案是可以。

考虑到斐波那契数列的第 n n n 项实际上仅仅只依赖于其前两项,那么我们也没必要开那么大的数组来把前面的每一步结果都保存下来,因此再进一步优化的代码如下:

int Fibonacci(int n)
{
	int a=1,b=1,temp;
	for(int i=2;i<=n;i++){
		temp=a+b;
		a=b,b=temp;
	}
	return b;
}

由于斐波那契数列本身正代表着动态规划这一算法中的入门,因此我们通常把具有斐波那契数列这种特性的题目统称为斐波那契问题。而斐波那契问题在动态规划中也占有相当一部分比例,下面列出几个较为经典的例子。




—— 问题一 :青蛙跳台阶(微软面试题)——


问题描述

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n n n 级的台阶总共有多少种跳法。

算法分析

我们来寻找递推关系,由题可知青蛙在每次跳跃时都会有以下两种方式:

  1. 跳 1 阶:只有一种跳法;
  2. 跳 2 阶:可以一次跳 2 阶,也可以跳两次 1 阶,共两种跳法;

而在 3 阶及 3 阶以上的情况下(假设为 n n n 阶),我们都可以将这种情况拆分为 1 和 2 这两种情况之和。逆向思考:假设青蛙要在最后跳上第 n n n 阶,那么其可以是从第 n − 1 n-1 n1 阶跳 1 1 1 阶来到这里,也可以是从第 n − 2 n-2 n2 阶跳 2 2 2 阶来到这里,有且仅有这两种跳法。因此,我们便找到了递推公式: f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2)。不难发现,这个递推公式和斐波那契数列中的公式一致,因此其代码也是一致的,在此就不再给出。

青蛙跳台阶




—— 问题二: 铺设瓷砖——


问题描述

有一条长度为 N ( 1 ≤ N ≤ 10 ) N(1≤N≤10) N(110) 的小路,现给两种不同地板:一种长度为 1,另一种长度为 2,数目不限。若要将这个长度为 N N N 的小路都铺上地板,问有多少种不同的铺法?

例如,长度为 4 的地面一共有如下 5 种铺法:

  • 4 = 1 + 1 + 1 + 1;
  • 4 = 2 + 1 + 1;
  • 4 = 1 + 2 + 1;
  • 4 = 1 + 1 + 2;
  • 4 = 2 + 2。
算法分析

N = 1 N=1 N=1 时,显然只有一种方案;
N = 2 N=2 N=2 时,有 2 两种方案(分别是 2=1+1 和 2=2);
N ≥ 3 N≥3 N3 时(假设为 n n n),对于当前 f ( n ) f(n) f(n) 这个状态,其前面的状态只有两种(和青蛙跳台阶的实质相同):

  1. 前面所有地板长度之和为 n − 1 n-1 n1
  2. 前面所有地板长度之和为 n − 2 n-2 n2

因此,我们便找到了递推式: f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)
可以发现,这个递推公式和斐波那契数列中的公式一致,因此其代码也是一致的,在此就不再给出。




—— 问题三:填格子——


问题描述

假设可以用 2 × 1 2\times1 2×1 的小矩形横着或竖着去覆盖更大的矩形(类似于在一个房间中贴瓷砖)。请问用 n n n 2 × 1 2\times1 2×1 的小矩形无重叠地覆盖一个 2 × n 2\times n 2×n 的大矩形,总共有多少种方法?
n=2

算法分析

依然来寻找递推关系:

  • n = 1 n=1 n=1 时,显然只有一种;

  • n = 2 n=2 n=2 时,有两种(如下图所示):
    n=2

  • n = 3 n=3 n=3时,有三种(如下图所示):
    n=3

n > 3 n>3 n>3 时(假设为 k k k),怎么做呢?
有且仅有两种办法:从 k − 1 k-1 k1 的地方竖着放一块、从 k − 2 k-2 k2 的地方横着放两块。
这样一看,就又回到了斐波那契式递归上了,即: f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)




—— 问题四:字符串转换——


问题描述

给定一个由 0 − 9 0-9 09 组成的字符串,1 可以转化成 A,2 可以转化成 B。依此类推。25 可以转化成 Y,26 可以转化成 Z,给一个字符串,返回能转化的字母串的有几种?
比如,123的转换方式有:

  • 1 、2 、3分别变成 A、B、C;
  • 12 、3分别变成 L、C;
  • 1 、23分别变成 A、W。

即共有三种可能的转换(ABC、LC、AW),即返回 3。
又比如 99999 只有一种转换:IIIII,故返回 1。

算法分析

其实本题的实质是求在第 k k k 个位置及其之前的数字串共能转化为多少种字母序列。
对第 k k k 个数字而言,其仅有两种转换方法:

  1. 数字 k k k 自己转换成自己对应的字母;
  2. 数字 K K K 和前面那个数字组成两位数(要求该数小于等于 26),然后再转换成对应的字母。

根据这两个转换规则,我们在程序中可以将其描述为一个遍历数字字符串的过程,其算法流程如下:

  1. 循环遍历给出的数字字符串;
  2. 对于数字串的第 k k k 个位置,判断第 k − 1 k-1 k1(如果存在)个数字和第 k k k 个数字组成的两位数是否大于 26,如果大于就没有第二种转换,则只有第一种转换方式,因此转移方程为: f ( i ) = f ( i − 1 ) f(i)=f(i-1) f(i)=f(i1);如果不大于 26(但大于9),则两种转换方式都有,则此时的状态转移方程为: f ( i ) = f ( i − 1 ) + f ( i − 2 ) f(i) = f(i-1) + f(i-2) f(i)=f(i1)+f(i2)


END


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

theSerein

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值