【剑指offer - C++/Java】10、矩形覆盖

在线题目链接:矩形覆盖

1 题目描述

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

2 题目分析

乍一看感觉无从下手!!!其实如果我们使用归纳方法的话,总能够总结出规律来。

假设下面是一个2*n的大矩形:

在这里插入图片描述

我们要使用下面这样的2*1的小矩形来填满上述的大矩形:
在这里插入图片描述

我们假设将2*n的矩形填满需要f(n)种方法

那么填充的方法可以有下面的两种方法:

  1. 如下图:2*1的矩形竖着放,那么还剩下2*(n-1)大小的矩形需要填,也就是剩下的矩形还有f(n-1)的方法填满

在这里插入图片描述

  1. 如下图:2*1的矩形横着放,那么由于它下面的也只能横这放,所以还剩下2*(n-2)个矩形,也就是剩下的矩形需要有f(n-2)种方法填满。

在这里插入图片描述

很明显,到这里我们就可以列出一个公式:

  • f(n)=f(n-1)+f(n-2)

这个公式我们太熟悉了,这就是斐波那契数列,我们看下面三篇文章的算法题都是这个公式的应用:

综上以及题目要求,我们得出综合公式(n=0时f(n)=0):
在这里插入图片描述

由此可以写出三种不同的代码:递归,动态规划,循环

3 代码

3.1 递归方法

递归方法很简单,直接利用公式即可。

3.11 Java代码

public class Solution {
    public int RectCover(int target) {
        //递归
        if(target<3)return target;
        return RectCover(target-1)+RectCover(target-2);
    }
}

3.12 C++代码

class Solution {
public:
    int rectCover(int number) {
        //递归
        if(number<3)return number;
        return rectCover(number-1)+rectCover(number-2);
    }
};

我们都知道这种递归解法会有很多重复的计算,就像下面的,假设我们要计算f(10):
在这里插入图片描述

计算f(10),要重复计算两次f(8),三次f(7),三次f(6),这种重复计算,对于数据比价大的时候,开销是非常大的。所以我们经常说,递归虽然好写,但是不建议在实现算法的时候使用递归算法。

3.2 动态规划算法

3.2 动态规划

凡是能用递归写出的代码,一定能够用动态规划写出来。

我们知道递归是为了求某一个值,而必须先知道另外的几个值后才能求出来。而想要求那另外的几个值,还需要再求另外的另外的值,就像上面的递归二叉树,想要先求f(10),必须知道f(9)和发(8)。想要知道f(9)又得知道f(8)和f(7)…

上面递归是想要计算总体值,需要求局部的值,想要求局部的值,又要求局部的局部的值。

动态规划不是这样,动态规划是先从递归的终止条件开始计算,也就是说,动态规划先计算局部的值,然后根据局部的值的累积,最终得到整体要求的值。也就是与递归反过来了。

比如针对上面的求f(10),我们先求f(1),f(2),f(3)…最终肯定会求得f(10)。这样我们就没有进行重复的计算。每一项都是只计算一次。

看代码就能明白上面说的是什么意思了。下面的ret数组,ret[i]代表斐波那契数组的第i项。我们要求得第n项,最后求到ret[n]直接返回即可。

3.21 Java代码

public class Solution {
    public int RectCover(int target) {
        //这同样是斐波那契数列 f(n)=f(n-1)+f(n-2)
        //动态规划
        if(target<=2)return target;
        int[] ret=new int[target+1];
        ret[1]=1;
        ret[2]=2;
        int i;
        for(i=3;i<=target;i++){
            ret[i]=ret[i-1]+ret[i-2];
        }
        return ret[target];
    }
}

3.22 C++代码

class Solution {
public:
    int rectCover(int number) {
         //这同样是斐波那契数列 f(n)=f(n-1)+f(n-2)
        //动态规划
        //if(number<=2)return number;
        int ret[number+1];
        ret[0]=0;
        ret[1]=1;
        ret[2]=2;
        int i;
        for(i=3;i<=number;i++){
            ret[i]=ret[i-1]+ret[i-2];
        }
        return ret[number];
    }
};

3.3 循环方法

所有的递归都可以写成动态规划,同理所有的动态规划,也一定能写成循环。只不过有的动态规划不好写成循环而已。本题是非常好写成循环的。

循环比动态规划好的原因在于,循环只用几个变量,循环使用它们得到最终结果,不保存之前的计算结果,动态规划却需要开辟一个数组,将所有计算过的结果保存,这很浪费空间。

3.31 Java代码

public class Solution {
    public int RectCover(int target) {
        //循环
        if(target<3)return target;
        int r1=1,r2=2,ret=0;
        int i;
        for(i=3;i<=target;i++){
            ret=r1+r2;
            r1=r2;
            r2=ret;
        }
        return ret;
    }
}

当然,上面使用三个变量,我们还可以再减少一个变量,使用两个变量:

public class Solution {
    public int RectCover(int target) {
        //循环,更加单的写法
        if(target<3)return target;
        int r1=1,r2=2;
        int i;
        for(i=3;i<=target;i++){
            r2+=r1;
            r1=r2-r1;
        }
        return r2;
    }
}

3.32 C++代码

三个变量

class Solution {
public:
    int rectCover(int number) {
        //循环
        if(number<3)return number;
        int r1=1,r2=2,ret=0;
        int i;
        for(i=3;i<=number;i++){
            ret=r1+r2;
            r1=r2;
            r2=ret;
        }
        return ret;
    }
};

两个变量

class Solution {
public:
    int rectCover(int number) {
        //循环,更加单的写法
        if(number<3)return number;
        int r1=1,r2=2;
        int i;
        for(i=3;i<=number;i++){
            r2+=r1;
            r1=r2-r1;
        }
        return r2;
    }
};

4、总结

注意学会递归,动态规划,循环三者时间的关系

探讨学习加:
个人qq:1126137994
个人微信:liu1126137994

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值