算法——递归
时间:2022-07-23
本文章向大家介绍算法——递归,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
背景
最近遇到一个类似爬楼梯的算法题。索性对递归的处理总结一下。
爬楼梯题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。示例 1:输入:2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶示例 2:输入:3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
解法1
int climbStairs(int n) {
if(n == 1) return 1;
else if(n == 2) return 2;
else return climbStairs(n-1) + climbStairs(n-2);
}
解法2(递归代码要警惕重复计算)
Map<Integer,Integer> map = new HashMap();
public int climbStairs(int n) {
if(n==1) return 1;
else if(n == 2) return 2;
else if(map.containsKey(n)) return map.get(n);
else {
int result = climbStairs(n-1) + climbStairs(n-2);
map.put(n,result);
return result;
}
}
解法3(改非递归)
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
int pre2 = 1;
int pre1 = 2;
int result =0;
for(int i=3;i<=n;i++){
result = pre1+pre2;
pre2 = pre1;
pre1 = result;
}
return result;
}
总结
如上,这道题目的解法主要还是应用了递归的编程技巧。递归,去的过程称为“递”,回来的过程称为“归”。一般所有的递归问题都可以用递归公式来解决。写出递归公式,问题就解决了一多半。
f(n) = f(n-1)+1 其中 f(1)=1
递归需要满足的三个条件
- 一个问题的解可以分解为几个子问题的解;又想起曾经学过的那篇初中课文《走一步,再走一步》,这就是分解子问题的过程。当一个人目标意识太强的时候,很容易迷失自我。其实我们求解递归问题,以为是如此,我们求解当前问题的值,或许只是上一个问题的值+1;
- 这个问题与分解后的子问题,除了数据规模不同,求解思路一样;
- 存在递归终止条件;
防止堆栈溢出
在jvm中,“栈”又称“堆栈”。每个方法在执行的过程中都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。如果递归深度多大,导致栈不够用,就会导致 StackOverflowError。如解法1,当n足够大,就会导致这个问题。
如何尽量避免?
- 限制递归深度;比方说当递归深度到达100的时候,就停止递归。但这种方法应用条件较为苛刻,对于一些对结果要求不严格的业务才可使用。
- 递归代码要警惕重复计算;(解法2) 比方说f(5)= f(4)+f(3)需要计算f(4)和f(3),那么我们计算f(4) = f(3)+f(2)还需要计算f(3)。f(3)被计算了2次,这个就是重复计算。避免重复计算可以使用散列表等数据结构将曾经计算过的结果缓存起来。
- 递归代码改非递归代码;(解法3) 很多递归代码都可以使用循环迭代的方式来替换,这样就解决了频繁压栈带来的溢出问题;
- 自己实现栈;在虚拟机中栈的深度受栈帧大小影响,当前可用深度不好确定。不如我们自己实现一个栈,对每个计算结果压栈,用到计算参数时,再出栈。整个过程就由我们自己控制了;
时间复杂度
解法1在实际应用中很容易超时,因为时间复杂度太高。那么怎么计算递归算法的时间复杂度呢?其实在所有的递归问题中,因为是大问题拆分小问题的思路,所以整个计算过程算下来就好像是一棵树。
假定加法时间复杂度忽略为1,那么第一层做加法1次,为1;第二次做2次,为2,第三层做4次为4...。这棵树最高为n,最低为n/2;当高为n时,所有层加和为2n-1,当高为n/2时,所有层加和为2n/2-1,无论如何,它的时间复杂度为指数级的,当n足够大时,是很恐怖的。这就是利用递归树求解递归的时间复杂度。
以上。。。
王争 《数据结构和算法之美》
- Leetcode 284. Peeking Iterator
- Leetcode 283. Move Zeroes
- Leetcode 282. Expression Add Operators
- Leetcode 279. Perfect Squares
- Leetcode 278. First Bad Version
- Leetcode 275. H-Index II
- Leetcode 274. H-Index
- 值得 .NET 开发者了解的15个特性
- Angular和Vue.js 深度对比
- 前端开发者常用的9个JavaScript图表库
- 1000多个项目中的十大JavaScript错误以及如何避免
- SoapUI实践:自动化测试、压力测试、持续集成
- 如何把kotlin+spring boot开发的项目部署在tomcat上
- 使用开源项目Alipay.AopSdk.Core完成支付宝网页登录
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python 基于jwt实现认证机制流程解析
- Keras – GPU ID 和显存占用设定步骤
- PHP中如何使用Redis接管文件存储Session详解
- 看我一波,Android获取进程名函数,代码优化到极致的操作!
- laravel5使用freetds连接sql server的方法
- opencv 图像滤波(均值,方框,高斯,中值)
- opencv 阈值分割的具体使用
- 浅谈keras 的抽象后端(from keras import backend as K)
- 在Keras中利用np.random.shuffle()打乱数据集实例
- 浅谈matplotlib中FigureCanvasXAgg的用法
- Keras自定义实现带masking的meanpooling层方式
- 利用keras使用神经网络预测销量操作
- 获取python运行输出的数据并解析存为dataFrame实例
- 如何使用Cython对python代码进行加密
- PHP快速排序算法实现的原理及代码详解