递归
参考极客时间
满足的三个条件
住: 主要是递推公式,先例举几项,在找规律,最后总结递推公式
题目1:爬楼梯
- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。参考
var climbStairs = function(n) {if(n === 1) return 1if(n === 2) return 2return climbStairs(n-1) + climbStairs(n-2)};climbStairs(2) // 2climbStairs(3) // 3
性能优化
- 添加边界条件
- 重复计算
- 我们可以通过一个数据结构(比如散列表)来保存已经求解过的值,当递归调用该值时,可以先去数据结构里看下是否已经求解过了,这样可以减少很多不必要的计算
// 函数的记忆优化function memorize(fn){var cache = {}return function(){// 定义一个独一无二的 key,来存valuevar key = arguments.length + Array.prototype.join.call(arguments)console.log('key', key)if(cache[key]){return cache[key]}cache[key] = fn.apply(this, arguments)return cache[key]}}const func = memorize(climbStairs)// 比如 n=30 时, 采用 memorize 执行时间明显降低
将递归代码改写为非递归代码
空间复杂度降低,执行时间也得到大幅度提升
var climbStairs = function(n) {if(n === 1) return 1if(n === 2) return 2let res = 0let per = 2let prepre = 1for(let i =3; i <= n; i++) {res = per + prepre // 前两项和prepre = per // prepre 值 变成 perper = res // per 值变成 每次计算的结果}return res}
题目2:子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。参考leetcode-78/*** @param {number[]} nums* @return {number[][]}*/var subsets = function(nums) {// 递归选与不选的问题const ans = []const temp = []const n = nums.lengthconst recur = (nums, i) => {// 边界if (i === n) {ans.push([...temp])return}// 不选 i, 下一个recur(nums, i + 1)// 选 i,下一个temp.push(nums[i])recur(nums, i + 1)// 还原现场temp.pop()}recur(nums, 0)return ans};
题目2:组合
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例:输入:n = 4, k = 2输出:[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],]
求解和子集思路一样,都是选与不选问题,只不过这里加了条件,要选几个
/*** @param {number} n* @param {number} k* @return {number[][]}*/var combine = function(n, k) {const ans = []const temp = []const recur = (i) => {// 这里可以使用剪枝处理// 1.temp 的值数量已经大于k// 2.temp 值得数量 + 剩余的数(n - i + 1)都不够k个// if (temp.length > k || temp.length + (n - i + 1) < k) return// 递归边界if (i === n + 1) { // n 本身也要if (temp.length === k) {ans.push([...temp])}return}// i不选recur(i + 1)// 选itemp.push(i)recur(i + 1)// 还原现场temp.pop()}recur(1)return ans};
总结
递归有利有弊,利是递归代码的表达力很强,写起来非常简洁;而弊就是空间复杂度高、有堆栈溢出的风险、存在重复计算、过多的函数调用会耗时较多等问题。所以,在开发过程中,我们要根据实际情况来选择是否需要用递归的方式来实现
