递归
- 思想
- 什么是递归
- 递归与循环
- 区别
- 优缺点
- 使用
- 步骤
- 优化
- 哈希表
- 迭代
- 例题
- 斐波那契数列
- 爬楼梯
- 合并两个有序链表
思想
什么是递归
- 递归算法用一句话概括就是:自己不停调用自己,直到问题解决。
- 它适用于可以将问题不断简化,且子问题和原问题的求解过程类似的问题。
递归与循环
区别
从递归的概念看起来,递归很像循环,都是对一段代码的复用,那么他们的区别在哪里呢?
- 递归由“归”过程。递归由return实现将结果“归”到上一层,像一个回旋镖,扔出去,再飞回来;而循环更像是射箭,扔出去,直到达到终止条件。
- 递归往往是自顶向下,而循环既可以自顶向下,也可以自底向上。
优缺点
- 递归运行效率低,循环运行效率高。因为递归是对函数的复用,而函数放在栈中,所以递归会造成不断的函数入栈,出栈。
- 递归易于理解。
使用
那么我们在使用的时候应该选择哪种呢?
递归:
- 数据的结构形式是按照递归定义的,比如单链表,二叉树,斐波那契数列等;
- 数据的结构形式不是按照递归定义的,但是用递归求解比用循环求解更加简单,比如汉诺塔问题,四重及以上循环问题。
循环:
- 数据的结构形式不是按照递归定义的,使用循环就能够轻松解决的问题,比如一重循环、二重循环、三重循环。
步骤
使用递归要从以下两方面着手:
- 边界(结束条件)。
意义:递归会在函数内部代码中,调用这个函数本身。所以,必须要找出递归的结束条件,否则会无限调用。
实施:我们需要找出当参数为何值时,递归结束,之后直接把结果返回。此时们必须达到根据这个参数的值,可以直接知道函数的结果。 - 状态转移方程式。
意义:每个阶段和下个阶段的关系。
实施:原函数的一个等价关系式,例如:f(n) = n+f(n-1), f(n) =n * f(n-1)等等。
优化
在递归的使用中,经常会出现重复运算,所以可以进行优化操作。
如:现在有一个问题,结果研究得出状态转移方程式是:F(n)=F(n-1)+F(n-2)。
此时我们的运算是以下情况:
其中,F(n-2),F(n-3)等等,都被重复计算了多次,所以我们需要优化。
哈希表
我们可以将第一次运算结果存放进哈希表,在下次运算时直接取出,这样就会避免重复运算。也就是用空间换时间。
迭代
迭代就是知道了一组小规模问题的答案后,就可以用状态转移方程组装成大一点规模问题的答案的做法。也就是自底向上的求解问题。
- 如上一个图,我们从底部向上移动,可以直接避免重复运算。
- 自底向上的计算方式还可以优化存储。如利用变量,滚动数组。
例题
斐波那契数列
- 题目出处
- 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。
//优化:
//1.迭代:由下向上,优化掉二叉树的多余节点
//2.滚动数组:优化内存空间
if(n==0){return 0}else if(n==1){return 1}else{ //优化:if(n<2){return n}else{
var count = Math.floor(n/3) //优化:三个三个跳,一个一个跳(各有优劣)
var myarr = [] //滚动数组不是说必须用数组,三个数字就可以
myarr[0]=0
myarr[1]=1
myarr[2]=myarr[0]+myarr[1]
if(count==0){return myarr[2]}
for(var i=1;i<=count;i++){
myarr[0]=(myarr[1]+myarr[2])%1000000007
myarr[1]=(myarr[2]+myarr[0])%1000000007
myarr[2]=(myarr[0]+myarr[1])%1000000007
}
if(n%3==0){
return myarr[0]
}else if(n%3==1){
return myarr[1]
}else{
return myarr[2]
}
}
};
爬楼梯
- 题目出处
- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
//看到n,想到与n-1的关系:错,没有理解精髓
//看到n,想到子问题=>此题应为少走一步=>f(n-1)与f(n-2)
//也不应该是看到n就想n-1,而是思考n问题从逻辑上是否与子问题有联系
/*if(n==1){
return 1
}else if(n==2){
return 2
}else{
return climbStairs(n-1)+climbStairs(n-2)
}*/
//时间复杂度:O(2的n)递归本质上是没有性能问题的,之所以出现问题是因为重复递归导致的,这里需要去避免重复递归的问题。
//根据运算过程,写出每一次的运算结果,这是一颗大量重复的二叉树
var climbStairs = function(n) {
const dp = [];
dp[0] = 1;
dp[1] = 1;
for(let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
};
//时间复杂度:O(n):对于迭代自下而上的构建二叉树,去掉了所有重复情况,是最简单的二叉树
//「滚动数组思想」可以用来优化空间复杂度
合并两个有序链表
- 题目出处
- 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2) {
if (l1 === null) {
return l2;
} else if (l2 === null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};