N级台阶,一次上1级或2级或3级或M级,总共有多少种走法

思路

先分析最简单的,也就是每次要不上1级,要么上2级。

这个问题需要反过来思考才能比较容易的找到规律。总共有N级台阶,因为每次要么上1级要么上2级,因此对于第N级台阶来说,它的前一步要么是在N-1级处要么是在N-2级处。在N-1级处时上1级到N级台阶,在N-2级处时上2级到N级台阶。因此,可以用公式表示为f(n)=f(n-1)+f(n-2),其中f(n)表示从第一级开始到第n级有多少种走法。当然,除了逆向思考外,也可以正面寻找规律。因为每次要不上1级要不上2级,那么在刚开始时,有两种选择,1或2,如果上1级,那么还剩n-1级,如果上2级,那么还剩n-2级,所以跟逆向思维一样得到f(n)=f(n-1)+f(n-2)。这就是一个典型的递归问题了。像这种公式,在数列中有一个比较著名的数列叫做“斐波那契数列”。

 

一次走1级或2级的总走法问题

 

解法一:递归法( 复杂度O(N!) )

public class Main {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		fun(10);
		System.out.println("n=10时运行时间:"+(System.currentTimeMillis() - start));
		start = System.currentTimeMillis();
		fun(30);
		System.out.println("n=30时运行时间:"+(System.currentTimeMillis() - start));
		start = System.currentTimeMillis();
		fun(40);
		System.out.println("n=40时运行时间:"+(System.currentTimeMillis() - start));
	}
	public static int fun(int stair){
		if(stair == 1){
			return 1;
		}else if(stair == 2){
			return 2;
		}else{
			return fun(stair - 1)+fun(stair - 2);
		}
	}
}

输出结果

可以看到当n增大时,时间复杂度会程指数级增加,这样递归算法就崩溃了。因为递归算法会有大量的重复计算,时间复杂度太高O(n!)。因此可以采用将之前计算的结果保存到map中,在后续需要用的时候直接取出来用而不用重复计算(动态规划)。这样时间复杂度就大大降低了。

 

解法二:动态规划法

public static int climbStairs(int n,Map<Integer,Integer> map){
		if(n <= 0)
			return 0;
		if(n <= 2)
			return n;
		if(map.containsKey(n)){
			return map.get(n);
		}else{
			int temp = climbStairs(n - 1,map) + climbStairs(n - 2,map);
			map.put(n, temp);
			return temp;
		}
	}

除了上面的递归法和动态规划法之外,这里还有另外一种方法来计算每次上1级或2级时的总走法——循环法(自命名的)。根据递归的过程可知,最终都会转化成计算f(1)或f(2),最后结果是a个f(1)与b个f(2)*2的和。因此可以先通过循环的方式求a和b,最后直接计算即可。先通过下表找a和b与n之间的规律:

nab
110
201
311
412
523

 

可以发现,第n项的a和b分别是其前两项的a的和。这就简单了,下面是循环方式的代码。

 

解法三:循环法

public static int climbStairs2(int n){
		if(n == 0)
			return 0;
		if(n == 1)
			return 1;
		if(n == 2)
			return 2;
		int lastF1 = 1,lastF2 = 0,nextF1 = 0,nextF2 = 1;
		int resultF1 = 0,resultF2 = 0;
		for(int i=3;i<=n;i++){
			resultF1 = lastF1 + nextF1;
			resultF2 = lastF2 + nextF2;
			lastF1 = nextF1;
			lastF2 = nextF2;
			nextF1 = resultF1;
			nextF2 = resultF2;
		}
		return resultF1+2*resultF2;
	}

上述三种方法在n=44时的运行时间比较如下:

 

 

一次走1级或2级或3级的总走法

这种情况只比一次走1级或2级的多了一个选择3而已,因此也可以直接使用递归的方式实现。

解法一:递归法

public class Main {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		fun(10);
		System.out.println("n=10时运行时间:"+(System.currentTimeMillis() - start));
		start = System.currentTimeMillis();
		fun(30);
		System.out.println("n=30时运行时间:"+(System.currentTimeMillis() - start));
		start = System.currentTimeMillis();
		fun(40);
		System.out.println("n=40时运行时间:"+(System.currentTimeMillis() - start));
	}
	
	public static int fun(int stair){
		if(stair == 1){
			return 1;
		}else if(stair == 2){
			return 2;
		}else if(stair == 3){
			return 4;//3级台阶总共有4种走法
		}else{
			return fun(stair - 1)+fun(stair - 2)+fun(stair - 3);
		}
	}
}

输出结果

同样的,递归方法的时间复杂度太高,需要考虑使用其他的方法实现。对于是否能够像解只有1或2级的那样找到a,b,c与n的规律进行求解,我没有尝试,感觉应该是有规律的(感兴趣的可以找一下规律)。这里只使用了动态规划法来降低时间复杂度。

解法二:动态规划法

public static int fun1(int stair,Map<Integer,Integer> map){
		if(stair == 1){
			return 1;
		}else if(stair == 2){
			return 2;
		}else if(stair == 3){
			return 4;//3级台阶总共有4种走法
		}else if(map.containsKey(stair)){
			return map.get(stair);
		}else{
			int temp = fun1(stair - 1,map)+fun1(stair - 2,map)+fun1(stair - 3,map);
			map.put(stair, temp);
			return temp;
		}
	}

上述两种方法在n=30时的运行时间比较如下:

 

问题拓展:一次至少上1级,至多上m级 ,有多少种走法?


这个问题是此类问题的最一般形式。就是每次的步数不固定,有m种可能。跟前面的类似,也可以使用递归的方式解决。只是这里因为步数不固定,所以要用for循环来对每一种可能的步数进行递归操作。当剩下的台阶数小于m时,那么每次就有剩余台阶数种可能的选择。直到最终剩余台阶数为0时,说明一种走法结束了。因此此递归函数的终止条件就是剩余台阶数为0。

同样的,为了减少重复计算,采用map保留以前的计算结果。


动态规划法

public static int countScheme(int n, int m, Map<Integer, Integer> map) {
		int count = 0;
		if (n== 0) {
			return 1;
		}
		if (map.containsKey(n)) {
			return map.get(n);
		} else if (n>= m) {
			for (int i = 1; i <= m; i++) {
				count += countScheme1(n- i, m, map);
			}
		} else {
			count += countScheme(n, n, map);
		}
		map.put(n, count);
		return count;
	}

当 n=30,m=10时,不加map与加map的运行时间对比如下:

 

 总结

①此类问题,一般采用递归解决

②为了减少重复计算,使用map保留计算结果(动态规划)

③对于每次只有1和2两种选择的情况,还可以观察规律,使用迭代法求f(1)和f(2)的个数

④对于每次最多走m步的情况,只需要用for循环来迭代每一种步数,并确定终止条件为剩余台阶数为0即可

  • 16
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值