一分钟了解动态规划 体验一下递归的暴力美学
哈喽哈喽,我是你们的金樽清酒,今天呢我来带大家一分钟了解透彻递归的问题,体验一下递归的暴力美学。
首先我们来看一下leetcode上的经典问题 爬楼梯
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
ini
输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
markdown
输入: n = 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
首先看到这道题,你会怎么去做呢?我们分析一下,一层楼有一种方法,二层楼有两种解法,三层楼三种方法,四层楼呢,我们勉强可以推算出是五种方法,但是都说是勉强了,那么再多一楼层大脑就已经反应不过来了。但是我们可以找到一个规律,从三层楼开始,三层楼的方法等于一楼加二楼的方法,四楼等于三楼加二楼的方法。诶,假设一个函数f(n),那么就有f(n)=f(n-1)+f(n-2),但是知道这个公式有什么用呢?
什么是递归呢?
首先,递归并不是一种算法,他是一种函数功能的实现。递归是一种编程技术,它通过在函数内部调用自身来解决问题。在递归过程中,问题被分解为更小的子问题,每个子问题都可以通过相同的方法来解决。递归在许多问题中非常有用,因为它能够简化问题的表达和解决过程。
递归通常包含两个部分:基本情况和递归情况。基本情况是指问题的最简单形式,它不再需要递归调用来解决。递归情况是指问题仍然需要进一步拆分为更小的子问题,并通过递归调用来解决。
当一个函数被调用时,它会将当前的执行状态保存在一个称为"栈帧"的数据结构中。每个栈帧包含了函数的局部变量、参数和返回地址。当递归调用发生时,一个新的栈帧被创建并推入调用栈中。递归的结束条件通常是在某个点上达到基本情况,这时递归调用停止,并开始将结果从底层逐步返回到顶层。
递归可以用于解决许多问题,如数学中的阶乘、斐波那契数列等。然而,递归也可能导致性能问题,因为它会导致多次重复计算。为了避免这种情况,可以使用记忆化技术或尾递归优化。
总的来说,递归是一种强大的编程技术,可以用于解决许多问题。但在使用递归时,需要小心处理基本情况和递归情况,以确保递归可以正常终止,并且要注意性能方面的考虑。
什么情况下用递归呢?
- 问题可以通过将其拆分为更小的子问题来解决,每个子问题可以通过相同的方法解决。
- 问题的结构本身是递归的,例如树形结构和图形结构等。
- 递归可以使代码更加简洁和易于理解,特别是在与其他递归算法相关的领域中,如函数式编程。
- 在某些情况下,递归可以比迭代更加高效、更加清晰,因为它可以利用调用栈来保留程序状态。
需要注意的是,递归在某些情况下可能会导致性能问题,因为它会导致多次重复计算。因此,在使用递归时,需要注意选择正确的结束条件和递归调用的顺序,以确保递归可以正常终止,并且要注意性能方面的考虑。
废话不多说,我们来看一下这一题怎么用递归去解决呢?
js
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function(n) {
if(n===1) return 1
if(n===2) return 2
return climbStairs(n-1)+climbStairs(n-2)
};
我们来看一下为什么是这样去写呢?由上面的分析我们知道,f(n)=f(n-1)+f(n-2),那我们要返回的就是climbStairs(n-1)+climbStairs(n-2),函数是一个个的栈,因为我们不知道f(n-1)和f(n-2)是多少,所以函数本身就会不停的调用,直到找到可用值为止,那就开始'归'啦,一直'归'到我们需要的值。所以就这么简单几行代码就能实现这个功能啦,是不是很暴力!不过是不是真的这么简单呢?诶,有点小插曲。
但你提交的时候会告诉你,超出时间限制,测试用例没有完全通过。上文说过了递归也可能导致性能问题,因为它会导致多次重复计算。为了避免这种情况,可以使用记忆化技术或尾递归优化。
递归的优化 记忆化技术
在上述代码中,由于没有记忆技术,每一个f(n)的值都会重复递归好多次,重复计算,浪费性能导致超时,不过这都是小问题啦,我们用一点点记忆化技术就能解决这个问题。看下述代码。
js
const f=[];//全局
const climbStairs=function(n){
//退出条件
if(n===1) return 1
if(n===2) return 2
if(f[n]===undefined){
//函数嵌套函数,这就是递归
f[n]=climbStairs(n-1)+climbStairs(n-2)
}
return f[n];
}
对比第一个代码,我们的优化代码多定义了一个全局变量,然后将返回的值存在变量里面,这样就能减少好多的重复计算,优化了性能。
看,这样写我们是不是就通过了呢?
总结
递归是一种函数功能的实现,它不是一种算法。递归是一种编程技术,它通过在函数内部调用自身来解决问题。但是递归会进行大量的重复计算,影响性能,所以我们还需要一些优化手段。该说不说社会你归哥,人狠话不多,切起题来那是相当的快。