算法系列--递归

什么是递归

递归是一种应用非常广泛的算法,或者说是编程技巧。如DFS深度优先搜索,前中后序二叉树遍历等,都要用到递归。

递归的求解过程:一层一层向下求解,直至到第一层,这个过程叫做; 然后一层一层向上返回值,这个过程叫做

什么类型的问题适合递归求解

  • 一个问题可以分解为几个子问题;
  • 子问题与父问题求解方法一样;
  • 存在递归终止条件;

如何编码递归问题

  • 写出递推公式;
  • 写出终止条件;
  • 翻译成代码;

递归代码弊端及解决方法

  • 堆栈溢出:通过控制递归深度;
  • 重复计算:通过散列表存储计算值;
  • 函数调用耗时多:通过控制递归深度;
  • 空间复杂度高:转成非递归;

经典习题

  1. 爬楼梯:一次可以爬1层或者2层,问爬到n层有多少种方法; LeetCode 70
    解题思路

    • 爬到最后一层楼梯只有两种可能,从倒数第二层走一步,或者从倒数第三层走两步;因此得出公式f(n) = f(n-1) + f(n-2);
    • 一直往前倒推,只需知道爬到第一层和爬到第二层的方法,即可推出后面所有楼层方法;因此得出终止条件f(1) = 1, f(2) = 2;
    // 原始递归版
    function f(n) {
      if (n === 1) return 1
      if (n === 2) return 2
      return f(n-1) + f(n-2)
    }
    
    // 递归优化:散列表存储计算的值
    function f(n) {
      let map = new Map()
      return g(n)
      function g(n) {
        if (map.has(n)) return map.get(n)
        if (n === 1) return 1
        if (n === 2) return 2
        let temp = g(n-1) + g(n-2)
        map.set(n, temp)
        return temp
     }
    }
    
    // 动态规划
    function f(n) {
      let dp = [1, 2]
      for (let i = 2; i < n; i++) {
    		dp[i] = dp[i-1] + dp[i-2]
      }
      return dp[n-1]
    }
    
  2. 求二叉搜索树,任意两节点节点之间的和;LeetCode 938
    解题思路

    • 二叉搜索树,说明左节点一定小于右节点;求任意两节点之间的和,只需要遍历所有节点,满足条件的值累加即可;遍历方式,深度优先搜索;
    • node 为 null,返回即可。
    // 递归
    function fn1(root, L, R) {
      let sum = 0
      dfs(root)
      return sum
      // 前序遍历
      function dfs(root) {
    	if (!root) return
        if (root.val <= R && root.val >= L) {
          sum += root.val
        }
        if (root.val > L) {
          dfs(root.left)
        }
        if (root.val < R) {
    	  dfs(root.right)
    	}
      }
    }
    
  3. 重建二叉树,已知前序arr1,中序arr2;剑指offer07

    解题思路

    • 已知前序,中序两个数组,那么前序的第一个节点,一定是顶点;根据该顶点的值,在中序数组中,就可以分出左右子树的节点个数,根据该个数又可以求出左右子树的前序数组;同理,递归方式求解左右子树;
    • 当前序数组或者后续数组为空时,直接返回,这便是递归终止条件;
    // 已知前序,中序
    function fn(arr1, arr2) {
      let root = new TreeNode()
      // 终止条件
    	if (!arr1 || !arr2) {
        return null
      }
      // 根节点
      let rootValue = arr1[0]
      root.value = rootValue
      let index = arr2.indexOf(rootValue)
      root.left = fn(arr1.slice(1, index + 1), arr2.slice(0, index))
      root.right = fn(arr1.slice(index + 1), arr2.slice(index + 1))
      return root
    }
    
    // 已知后序,中序
    function fn(arr1, arr2) {
      if (!arr1 || !arr2) {
        return null
      }
      let root = new TreeNode()
      let n = arr1.length
      let rootValue = arr1[n-1]
      let index = arr2.indexOf(rootValue)
      root.value = rootValue
      root.left = fn(arr1.slice(0, index), arr2.slice(0, index))
      root.right = fn(arr1.slice(index, n-1), arr2.slice(index+1, n))
      return root
    }
    
  4. 第一行为0,接下来每一行都将0替换为01,1替换为10;求第N行,第K列字符;LeetCode779

    解题思路

    • 先按照规则先几行,然后找找第n行和第n-1行的规律;不难发现,第n行数量时第n-1行的2倍,且前半部分相同,后半部分是前半部分的相反数(即0的相反数为1,1的相反数为0);因此,递推公式为 k在前半部分,f(n, k) = f (n-1, k) ; k在后半部分, f(n,k) = f(n-1, k-2^(n-2)) === 0 ? 1 : 0;
    • 一直往前求解,得出终止条件为 f (1,1) = 0
    function fn(n,k) {
      if (n === 1) { return 0 }
      if (k <= Math.pow(2, n-2)) {
        return fn(n-1, k)
      } else {
        return fn(n-1, k - Math.pow(2, n-2)) === 0 ? 1 : 0
      }
    }
    
  5. 求二叉树中相同数字的最长路径;

    解题思路

    • 画出一个简单二叉树,通过查找发现,从下往上计算每个节点的路径;如果该节点值等于左节点,则该节点长度=左节点长度 + 1;否则为0; 同理右节点,最终该节点值为左节点路径+右节点路径;
    • 不妨后序遍历每个节点,过程中记录最长路径;
    • 遍历到顶点,即为终止条件;
    function fn(root) {
      let max = 0
      dfs(root)
      return max
      // 后序遍历
      function dfs(root) {
        if (!root) {
          return 0
        }
        let left = dfs(root.left)
        let right = dfs(root.right)
        let value = root.val
        if (root.left && value === root.left.value) {
          left += 1
        } else {
          left = 0
        }
        if (root.right && value === root.right.value) {
          right += 1
        } else {
          right = 0
        }
        // 最长路径可以经过该节点
        max = Math.max(max, left + right)
        // 该节点向上返回的,只能是最长的那一条路径
        return Math.max(left, right)
      }
    }
    
  6. 两个正整数的递归乘法实现;

    算法思路

    • 乘法换成累加,第n项与第n-1项的关系时, f(n, num) = f(n-1) + num;
    • 当n===1时,返回num,即为终止条件;
    function fn(A, B) {
      if (A === 1) {
        return B
      }
      return fn(A-1, B) + B
    }
    
  7. 给定一个二叉搜索树的根节点,返回任意两节点的差的最小值;

    算法思路

    • 二叉搜索树,第一时间应该联想到中序遍历,就是排序后的数组;容易发现,任意两节点的差的最小值,即为中序遍历后的数组中相邻两节点的最小值;
    • 当节点为null时,返回;
    function fn(root) {
      let min = Infinity
      let last = null
      dfs(root)
      return min
      // 中序遍历
      function dfs(root) {
        if (!root) return
        dfs(root.left)
        let value = root.val
        if (last === null) {
          last = value
        } else {
          last = value
          min = Math.min(min, value - last)
        }
        dfs(root.right)
      }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值