题目:
- 上楼梯
- 斐波那契数列
- 机器人走方格
- 硬币表示
1.上楼梯
题目描述:
有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶。 请实现一个方法,计算小孩有多少种上楼的方式。
为了防止溢出,请将结果Mod 1000000007
给定一个正整数int n,请返回一个数,代表上楼的方式数。
保证n小于等于100000。
首先有两种方法:
- 递归
- 迭代
(1)递归
分析:
首先,递归比较耗时,耗时的原因是递归不断地在调用栈,还有就是重复的运算比较多。37次已经不能秒杀了,递归的基本思想就是数学归纳。
下面先从小向大开始排列。
n为楼梯的阶数,k为上楼梯的方法种类数
n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | … | n |
---|---|---|---|---|---|---|---|---|---|
k | 1 | 1 | 2 | 4 | 7 | 13 | 24 | … | 前三位数字之和 |
此时我们发现上楼梯方法种类数是前三个数字之和。如果用递归来解决,也就是说需要逆向思考,我们上楼梯,不妨把它回放一下。从第n(n>6)个台阶开始往下退,因为一次只能下1或2或3个台阶,因此我们就有了从这样几种方法,从第n-1,n-2,n-3个台阶上到第n个台阶,那么n-1可以分为n-2,n-3,n-4这三种,n-2可以分为n-3,n-4,n-5这三种,n-3可以分为n-4,n-5,n-6这几种情况。一次这样分解下去,其实就是一颗完全三叉树的形式。直到树最下面的台阶为1或2或3时,就把所有的分支加起来就可以计算n的台阶数了。
递归三要素:
- 变量与不变量
- 递归退出的条件
- 递归表达式
代码如下()
//上楼梯-递归(当n为37左右需要等待很长时间,不能秒杀了,迭代可以)
public static final int mod=1000007;
static int stairs(int n)
{
if(n==0 || n==1) return 1;
if(n==2) return 2;
if(n==3) return 4;
return stairs(n-1)%mod+stairs(n-2)%mod+stairs(n-3)%mod;
}
(2)迭代
分析:也就是在循环里面,不断地改变那个k-1和k-2的值,让他循环加起来,得到k。
这样做时间是比较快的,37次秒杀。
代码如下:
//上楼梯-迭代
static int stairs_for(int n)
{
int x1=1;
int x2=1;
int x3=2;
int temp;
//x4=x1+x2+x3
//x5+x2+x3+x4
//xn=x(n-3)+x(n-2)+x(n-1);
for(int i=4;i<=n;i++)
{
temp=x1%mod;
x1=x2%mod;
x2=x3%mod;
x3=(temp+(x1+x2)%mod)%mod;
}
return x3;
}
2.斐波那契数列,类似题目
代码如下:
求第n项的递归形式
//斐波那契-递归(当n为45左右就需要很长时间了,不可秒杀,迭代可以)
static int fib(int n)
{
if(n==0 || n==1) return 1;
if(n==2) return 1;
return fib(n-1)+fib(n-2);
}
求第n项的迭代形式
//斐波那契数列求第n项-迭代
static int fib_for(int n)
{
int x1=1;
int x2=1;
int temp;
for(int i=3;i<=n;i++)
{
temp=x1;
x1=x2;
x2=temp+x1;
}
return x2;
}
求前n项和
//斐波那契数列求和-迭代
static int fib_sum(int n)
{
int x1=1;
int x2=1;
int temp;
int sum=1;
//x4=x1+x2+x3
//x5+x2+x3+x4
//xn=x(n-3)+x(n-2)+x(n-1);
for(int i=2;i<=n;i++)
{
sum=sum+x2;
temp=x1;
x1=x2;
x2=temp+x1;
}
return sum;
}
3.机器人走方格
题目描述:
有一个XxY的网格,一个机器人只能走格点且只能向右或向下走,要从左上角走到右下角。 请设计一个算法,计算机器人有多少种走法。
给定两个正整数int x,int y,请返回机器人的走法数目。
保证x+y小于等于12。
分析:
这道题和上面的有所不同,因为这涉及到二维的变化。首先,现列举1x1 1x2 2x1 2x2 2x3 3x2的情况,基本就可以得到3x3的答案了。
代码如下:
求第n项的递归形式
//机器人走方格-递归
static int robot(int x,int y)
{
if(x==1||y==1)return 1;
return robot(x,y-1)+robot(x-1,y);
}
求第n项的迭代形式
//机器人走方格-迭代
static int robot_for(int m,int n)
{
int [][] a=new int [m+1][n+1];
for(int j=0;j<n;j++)
a[0][j]=1;
for(int i=0;i<m;i++)
a[i][0]=1;
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
a[i][j]=a[i-1][j]+a[i][j-1];
}
}
return a[m-1][n-1];
4.硬币表示
题目描述:
假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合够成一个给定的数值n。 例如n=200,那么一种可能的组合方式为 200 = 3 1 + 1*2 + 1*5 + 2*20 + 1 50 + 1 * 100. 问总共有多少种可能的组合方式?
分析:
这道题比上面的变得又复杂了,但是万变不离其宗,只要找到其递归的变量与不变量,递归退出条件和递归表达式,就能够写出来。首先,输入值为n面值为1,5,10,25,按照递归的形式去测试一下,我们直观上的应该是从小王大找规律,而递归就是我们的你想表达的结晶。那我们直接从逆向思考,可见变量之一定是这个n,其次就是这个面值的变化,但是面值的变化是固定而有序的,那我们就把这个面值放在数组中,我们让下标递减,这样,我们就发现下标对应的面值的变化也是从25-10-5-1,到1时应该就是递归的出口了。还有一个重要的变量,就是每一个面值所需要的数量。这个数量跟n有关还跟已经使用面值和这个面值的数量有关,他还决定着最大面值的最大数量。
举例:
假设:给定数值为10分
10分面值的有0或者1个10分的,当有0个时,可以分给5分面值的,此时5分的可以有0,1,2个,0个时都用1分面值表示,1个时用5个1分表示,2个时不用1分表示。这样也就是四条路径。如果每个小圈有权值的话,每一支路径的和就都等于10,共有4条。
代码如下
//硬币表示
static int coin(int n,int value[],int cur)
{
int sum=0;//记录总数
if(cur==0) return 1;//当只用1分的面值时,也就是递归出口
//循环递归,也就是多分递归(例如机器人走方格时二分递归,每次有两条路径可走,这里就有不确定条路可走(重要因素:最大面值的使用数量))
for(int i=0;i*value[cur]<=n;i++)//从大面值的往小面值递归。
//剩余的分 放到下面继续递归也就是 n-i*value[cur]
sum+=coin(n-i*value[cur],value,cur-1);//返回值是最小的,最后累加到sum中
return sum;
}