《剑指offer》面试题10:题目4矩形覆盖(C++实现)

题目描述

我们可以用2×1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2×1的小矩形无重叠地覆盖一个2×n的大矩形,总共有多少种方法?

题目分析

我们设f(n)代表n个2×1的小矩形无重叠地覆盖一个2×n的大矩形的方法数目。
考虑第一步
1.第一个2×1的小矩阵如果竖着放,那么剩下的矩形右边还有2×(n-1)的空间,所以有f(n-1)种放法。
2.第一个2×1的小矩阵如果横着放,那么对应还需要一个2×1的小矩阵也横着放,那么剩下的矩形右边还有2×(n-2)的空间,所以有f(n-2)种放法。
故f(n)=f(n-1)+f(n-2),仍然是斐波那契数列。
f ( n ) = { 0 n=0 1 n=1 2 n=2 f ( n − 1 ) + f ( n − 2 ) n>2 f(n)=\left\{ \begin{aligned} 0&&\text{n=0} \\ 1&& \text{n=1}\\ 2&& \text{n=2}\\ f(n-1)+f(n-2)&& \text{n>2} \\ \end{aligned} \right. f(n)=012f(n1)+f(n2)n=0n=1n=2n>2

解决思路

经过刚才的分析,将题目建模并进行转换,我们要解决的问题其实最博主之前写的普通的青蛙跳台阶是一模一样的。

思路一:简单的递归

根据上面的思路分析,n<=2我们列情况解决即可。
n>3我们采用递归解决。
所以我们很快能写出如下的代码:

//运行时间:544ms
//占用内存:496k
class Solution {
public:
    int rectCover(int number) {
     int result[3]={0,1,2};
     if(number<3)
         return result[number];
     return  rectCover(number-1)+rectCover(number-2);
    }
};

很好,说明你还记得以前学的东西。但是我们现在是要马上去面试的人了,怎么能写这么low的代码呢?
说它low是因为上述的代码有很严重的递归问题,比如求解f(10),你需要求解f(9)和f(8)。而求解f(9),你又需要去求解f(8)和f(7)。
说到这里,你会发现上面的f(8)咱们就求解了两遍!如果是一个特别大的数字,那么重复求解的问题会呈指数级的急剧增加,所以我们得想个法子,把这个重复求解的问题把它解决了!

思路二:用数组记录f(n)

最简单的思路,就是用一个数组来记录下我们的f(n),每次计算f(n)前,我们先查询这个值是否计算过,如果没有,就执行递归调用计算它,并把它存储下来,下次直接调用就可以了。
如果有,那就更好了,咱们不用客气,直接拿来用,这样就省去了很多重复求解的问题。

//运行时间:3ms
//占用内存:476K
class Solution {
public:
    int rectCover(int number) {
    int memo[50]={-1};
        for(int i=0;i<3;i++)
            memo[i]=i;
        for(int i=3;i<=number;i++)
            memo[i]=memo[i-1]+memo[i-2];
        return  memo[number];
    }
};

思路三:自底向上计算(动态规划)

既然可以用递归实现,我们自然可以想到用动态规划去实现。
首先根据f(0)和f(1)去推算f(2),再根据f(1)和f(2)去推算f(3),依次类推,直到推算出我们要求的f(n),再把这个值返回即可。

//运行时间:4ms
//占用内存:480K
class Solution {
public:
    int rectCover(int number) {
         int result[3]={0,1,2};
        if(number<3)
            return result[number];
   
        int FibMinusTwo = 1;//定义f(n-1)
        int FibMinusOne = 2;//定义f(n-1)
        int FibN= 0;
        for(int i=3;i<=number;i++){
            FibN = FibMinusOne + FibMinusTwo;//f[i]=f[i-1]+f[i-2];
            FibMinusTwo = FibMinusOne;//更新f[i-2];
            FibMinusOne = FibN;//更新f[i-1];
          }       
        return  FibN;
    }
};

相对于思路二而言,思路三更加节省空间。

小结

思路一代码会有很多重复计算的地方,不太可靠。
思路二应用了memo来记录,避免了思路一里面很多重复计算的地方。
思路三相对于思路二而言,在占用内存上又有了更好的优化。

整体而言,思路三最佳。

参考文献

《剑指offer》

牛课网刷题链接link.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值