【一刷《剑指Offer》】面试题 9:斐波那契数列(扩展:青蛙跳台阶、矩阵覆盖)

ops/2024/9/24 12:33:13/

力扣对应链接:LCR 126. 斐波那契数 - 力扣(LeetCode)

牛客对应链接:斐波那契数列_牛客题霸_牛客网 (nowcoder.com)

核心考点:空间复杂度,fib 理解,剪枝重复计算。


一、《剑指Offer》内容


二、分析问题 

斐波那契数列是:0 1 1 2 3 5 8 13 21 ...

解题方式很多,有递归方式,也有动归(迭代)方式,但是都是最简单的方式。


三、代码 

1、方法一(迭代

//力扣AC代码
class Solution {
private:int MOD=1e9+7;
public:int fib(int n) {if(n==0) return 0;int first=1;int second=1;int third=1;while(n>2){third=(first+second)%MOD;first=second;second=third;n--;}return third;}
};//牛客AC代码
class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param n int整型 * @return int整型*/int Fibonacci(int n) {int first=1;int second=1;int third=1;while(n>2){third=first+second;first=second;second=third;n--;}return third;}
};

2、方法二(递归 + 剪枝)

直接用最简单的方式因为代码空间复杂度过高,过不了 OJ,所以可以采用 map 进行 “剪枝”。

//力扣AC代码
class Solution {
private:int MOD=1e9+7;unordered_map<int, int> filter;
public:int fib(int n) {if(n==0 || n==1) return n;if(n==2) return 1;int ppre=0;if(filter.find(n-2)==filter.end()){ppre=fib(n-2);filter.insert({n-2, ppre});}elseppre=filter[n-2];int pre=0;if(filter.find(n-1)==filter.end()){pre=fib(n-1);filter.insert({n-1, pre});}elsepre=filter[n-1];return (ppre+pre)%MOD;}
};//牛客AC代码
#include <unordered_map>
class Solution {
private:unordered_map<int, int> filter;
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param n int整型 * @return int整型*/int Fibonacci(int n) {if(n==0 || n==1) return n;if(n==2) return 1;int ppre=0;if(filter.find(n-2)==filter.end()){ppre=Fibonacci(n-2);filter.insert({n-2, ppre});}else ppre=filter[n-2];int pre=0;if(filter.find(n-1)==filter.end()){pre=Fibonacci(n-1);filter.insert({n-1, pre});}else pre=filter[n-1];return ppre+pre;}
};

四、相关错题

【错题集-编程题】Fibonacci数列(Fib 数列)-CSDN博客


五、扩展

青蛙跳台阶的问题中,如果把条件改成:一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... ...它也可以跳上 n 级,此时该青蛙跳上一个 n 级的台阶总共有多少种跳法?

可以用数学归纳法证明:f(n) = 2^(n-1)。

力扣对应链接:70. 爬楼梯 - 力扣(LeetCode)

牛客对应链接:跳台阶_牛客题霸_牛客网

核心考点:场景转化模型,模型提取解法,简单 dpfib。


1、分析题目

 动规三步骤:

  1. 定义状态:f(n):青蛙跳上第 n 级台阶的总跳法数。(到了 n,只能是从 n-1 或 n-2 跳过上来)
  2. 编写状态转移方程:f(n) = f(n-1) + f(n-2)。
  3. 设置初始值:f(0) = 1(0 台阶就是起点,到达 0 台阶的方法有一种,就是不跳(这里可能有点奇怪,但是如果方法次数为 0,就说明不可能开始)),f(1) = 1,f(2) = 2。

2、代码

(1)方法一(动态规划
//时间复杂度: O(n), 空间复杂度: O(N)
//牛客AC代码
class Solution {
public:int jumpFloor(int number) {//dp[n] = dp[n-1]+dp[n-2];int dp[number+1];dp[0] = 1;dp[1] = 1;for(int i = 2; i<= number;i++)dp[i] = dp[i-1] + dp[i-2];return dp[number]; //第number下标,就是第number阶台阶}
};//力扣AC代码
class Solution {
public:int climbStairs(int n) {if(n<=2) return n;vector<int> dp(n+1);dp[0]=1, dp[1]=1, dp[2]=2;for(int i=3; i<=n; i++)dp[i]=dp[i-1]+dp[i-2];return dp[n];}
};

(2)方法一(动态规划 + 优化)
//时间复杂度: O(n), 空间复杂度: O(1)
//牛客AC代码
class Solution {
public:int jumpFloor(int number) {int first=1;      //第一个台阶int second=2;     //第二个台阶int third=number; //等于number直接就考虑了dp[]1]=1 && dp[2]=2的情况while(number>2){third=first+second;first=second;second=third;number--;}return third;}
};//力扣AC代码
class Solution {
public:int climbStairs(int n) {if(n<=2) return n;int dp[3];dp[0]=1, dp[1]=1, dp[2]=2;for(int i=3; i<=n; i++){int sum=dp[1]+dp[2];dp[1]=dp[2];dp[2]=sum;}return dp[2];}
};

六、举一反三

牛客对应链接:矩形覆盖_牛客题霸_牛客网 (nowcoder.com)

核心考点:场景转化成模型,特殊情况分析,简单 dp。


1、分析题目

比如 n = 3 时,2*3 的矩形块有 3 种不同的覆盖方法(从同一个方向看):


那如果是用 8 个 2*1 的小矩形无重叠地覆盖一个 2*8 的大矩形(右图),总共又有多少种方法? 

我们先把 2*8 的覆盖方法记为 f(8)。用第一个 1*2 小矩形去覆盖大矩形的最左边时有两个选择,竖着放或者横着放。当竖着放的时候,右边还剩下 2*7 的区域,这种情形下的覆盖方法记为 f(7)。接下来考虑横着放的情况。当 1*2 的小矩形横着放在左上角的时候,左下角必须和横着放一个 1*2 的小矩形,而在右边还剩下 2*6 的区域,这种情形下的覆盖方法记为 f(6),因此 f(8) = f(7) + f(6)。

这不就是斐波那切数列的问题吗?反思一下,很多问题会包裹很多现实问题,解决问题的第一步往往是从实际问题中提炼出我们的解决问题的数学模型,然后再进行解决。这里也可以使用多种方法解决,不过我们这里重点用 dp,倒也不是说它是最优的,而是平时在写代码的时候,这种思想还是用得少,所以就多写一写。


用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,每次放置的时候,无非就两种放法,横着放或竖着放。

其中,横着放一个之后,下一个的放法也就确定了,所以虽然放置了两个矩形,但属于同一种放法。其中,竖着放一个之后,本轮放置也就完成了,也属于一种方法。

所以,当 2*n 的大矩形被放满的时候,它无非就是从上面两种放置方法放置来的。

下面继续使用 dp 来进行处理,我们发现斐波那契数列的方式也可以处理,因为前面已经讲过,这里就不再用这种方式来写了

  • 状态定义:f(n) : 用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形所用的总方法数。
  • 状态递推:f(n) = f(n-1)【最后一个竖着放】 + f(n-2)【最后两个横着放】。
  • 初始化:f(0) = 1(f(0) 这里可以不考虑,因为语义不清淅,如果考虑的话就把值设为 1,可参考前面的原因),f(1) = 1,f(2) = 2。

注意:这里需要充分考虑 n 是 [0,1] 时的情况,OJ 一般测试用例设计的比较全面,会有 0,1 传进来,这个时候后续的 dp[1] = 1; 就可能报错。


2、代码

(1)方法一(动态规划
//牛客AC代码
class Solution {
public:int rectCover(int number) {if(number<=2)return number;int dp[number+1];dp[1]=1, dp[2]=2;for(int i=3; i<=number; i++)dp[i] = dp[i-1]+dp[i-2];return dp[number];}
};

http://www.ppmy.cn/ops/19491.html

相关文章

springcloud按版本发布微服务达到不停机更新的效果

本文基于以下环境完成 spring-boot 2.3.2.RELEASEspring-cloud Hoxton.SR9spring-cloud-alibaba 2.2.6.RELEASEspring-cloud-starter-gateway 2.2.6.RELEASEspring-cloud-starter-loadbalancer 2.2.6.RELEASEnacos 2.0.3 一、思路 实现思路&#xff1a; 前端项目在请求后端接…

制造型企业 如何实现便捷的机台文件统一管理?

机台文件统一管理&#xff0c;这是生产制造型企业都需要去做的&#xff0c;机台文件需要统一管理的原因主要包括以下几点&#xff1a; 1、提高效率&#xff1a;统一管理可以简化文件的访问和使用过程&#xff0c;提高工作效率&#xff0c;尤其是在需要频繁访问或更新机台文件的…

微信小程序实时日志使用,setFilterMsg用法

实时日志 背景 为帮助小程序开发者快捷地排查小程序漏洞、定位问题&#xff0c;我们推出了实时日志功能。开发者可通过提供的接口打印日志&#xff0c;日志汇聚并实时上报到小程序后台。开发者可从We分析“性能质量->实时日志->小程序日志”进入小程序端日志查询页面&am…

K8s: 公有镜像中心和私有镜像中心的搭建

公有镜像中心的搭建和使用 1 &#xff09;在 官方docker镜像中心推送 在 hub.docker.com 上注册账号 (国内一般访问不了&#xff0c;原因不多说) 找到 Create Repository 按钮就行仓库的创建 这样就在官方创建了一个仓库&#xff0c;比如地址为: xx/y-y xx 是我的账户名y-y 是…

【网站项目】考研助手

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

ZIP压缩输入流(将文件压缩为ZIP文件)

文章目录 前言一、ZIP压缩输入流是什么&#xff1f;二、使用介绍 1.使用方法2.实操展示总结 前言 该篇文章将会介绍如何使用java代码将各种文件&#xff08;文件夹&#xff09;的资源压缩为一个ZIP压缩包。通过java.util包中的ZipOutputStream类来实现。并且需要自定义压缩方法…

富格林:可信方针实现安全盈利

富格林指出&#xff0c;现货黄金一直以来都是投资者们追捧的热门品种之一。其安全性和保值增值的特性吸引着广大投资者。然而&#xff0c;要在现货黄金市场中取得成功并非易事&#xff0c;是需要一定的可信技巧和方针来支撑的。下面富格林将介绍一些关键的可信方针&#xff0c;…

java:基于guava ClassPath工具实现基于包名(package)的类扫描

google的guava库提供了一个类路径扫描的实用工具ClassPath(参见说明&#xff1a; https://github.com/google/guava/wiki/ReflectionExplained#classpath)工具&#xff0c;适用于非android的Java平台搜索类。基于它可以设计一个过滤包名的搜索工具。 导入依赖库 <dependen…