递归的实现和特性

树的面试题解法一般都是递归。主要原因如下:

  • 节点的定义,本身就是以递归的方式进行的
  • 树、二叉树、搜索二叉树,存在重复性。

递归(Recursion),本质就是通过函数体来进行的循环。

递归模板

function recursion (level, param1, param2, ...): // 1. recursion terminator 递归终结条件 if level > MAX_LEVEL: process_result return // 2. process logic in current level 处理当前层逻辑 process(level, data...) // 3. drill down 下探到下一层 recursion(level + 1, p1, ...) // 4. reverse the current level staus if needed 清理当前层

注意点:

  • 不要人肉递归(画递归树)
  • 找到最近最简方法,将其拆解成可重复解决的问题(最近重复子问题)
  • 数学归纳法思维

爬楼梯(阿里巴巴、腾讯、字节跳动在半年内面试常考)

括号生成 (字节跳动在半年内面试中考过)

翻转二叉树 (谷歌、字节跳动、Facebook 在半年内面试中考过)

验证二叉搜索树(亚马逊、微软、Facebook 在半年内面试中考过)

二叉树的最大深度(亚马逊、微软、字节跳动在半年内面试中考过)

二叉树的最小深度(Facebook、字节跳动、谷歌在半年内面试中考过)

二叉树的序列化与反序列化(Facebook、亚马逊在半年内面试常考)

// 爬楼梯 // f(n) = f(n - 1) + f(n - 2),mutual exclusive、complete exhaustive /** * @param {number} n * @return {number} */ var climbStairs = function(n) { if (n <= 2) return n; let n1 = 1, n2 = 2; for (let i = 2; i < n; i++) { const n3 = n1 + n2; n1 = n2; n2 = n3; } return n2; };
// 括号生成 /** * @param {number} n * @return {string[]} */ var generateParenthesis = function(n) { const ret = []; function _generate (left, right, n, s) { if (left === n && right === n) { ret.push(s); return; } if (left < n) { _generate(left + 1, right, n, s + '('); } if (left > right) { _generate(left, right + 1, n, s + ')'); } } _generate (0, 0, n, ''); return ret; };
// 翻转二叉树 /** * @param {TreeNode} root * @return {TreeNode} */ var invertTree = function(root) { let curr; const preorder = (root) => { if (!root) return; curr = root.left; root.left = root.right; root.right = curr; preorder(root.left); preorder(root.right); } preorder(root); curr = null; return root; };
// 验证二叉搜索树 // BST - 中序遍历是递增的,有序的 /** * @param {TreeNode} root * @return {boolean} */ var isValidBST = function(root) { const inorder = (root, min = -Infinity, max = Infinity) => { if (!root) return true; if (root.val <= min || root.val >= max) return false; return ( inorder(root.left, min, root.val) && inorder(root.right, root.val, max) ) } return inorder(root); };
// 二叉树的最大深度 /** * @param {TreeNode} root * @return {number} */ var maxDepth = function(root) { if (!root) return 0; let deep = 0; const queue = [[root, 1]]; while (queue.length) { const [ root, l ] = queue.shift(); deep = Math.max(deep, l); root.left && queue.push([ root.left, l + 1 ]); root.right && queue.push([ root.right, l + 1 ]); } return deep; }; /** * @param {TreeNode} root * @return {number} */ var maxDepth = function(root) { if (!root) return 0; let deep = 1; const dfs = (root, l) => { if (!root) return; deep = Math.max(l, deep); root.left && dfs(root.left, l + 1); root.right && dfs(root.right, l + 1); } dfs(root, 1); return deep; };
// 二叉树的最小深度 /** * @param {TreeNode} root * @return {number} */ var minDepth = function(root) { if (!root) return 0; const queue = [[ root, 1 ]]; while (queue.length) { const [root, l] = queue.shift(); if (!root.left && !root.right) { return l; } root.left && queue.push([ root.left, l + 1 ]); root.right && queue.push([ root.right, l + 1 ]); } }; /** * @param {TreeNode} root * @return {number} */ var minDepth = function(root) { if (!root) return 0; let deep = 0; const dfs = (root, l) => { if (!root.left && !root.right) { // 首次赋值,必须是真实深度,不能给 deep 赋值默认值 deep = deep ? Math.min(deep, l) : l; } root.left && dfs(root.left, l + 1); root.right && dfs(root.right, l + 1); } dfs(root, 1); return deep; };
// 二叉树的序列化和反序列化 // 深度优先遍历 /** * Encodes a tree to a single string. * * @param {TreeNode} root * @return {string} */ var serialize = function(root) { const dfs = (root) => { if (!root) return '*'; const left = dfs(root.left), right = dfs(root.right); return `${ root.val },${ left },${ right }`; } return dfs(root); }; /** * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */ var deserialize = function(data) { const list = data.split(','); const dfs = (list) => { const rootVal = list.shift(); if (rootVal == '*') { return null; } const root = new TreeNode(rootVal); root.left = dfs(list); root.right = dfs(list); return root; } return dfs(list); }; // 广度优先遍历 /** * Encodes a tree to a single string. * * @param {TreeNode} root * @return {string} */ var serialize = function(root) { const queue = [root]; const ret = []; while (queue.length) { const root = queue.shift(); if (root) { ret.push(root.val); queue.push(root.left); queue.push(root.right); } else { ret.push('*') } } return ret.join(','); }; /** * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */ var deserialize = function(data) { if (data == '*') return null; const list = data.split(','); const root = new TreeNode(list[0]); const queue = [root]; let cursor = 1; while (cursor < list.length) { const root = queue.shift(); const leftVal = list[cursor], rightVal = list[cursor + 1]; if (leftVal != '*') { const leftNode = new TreeNode(leftVal); root.left = leftNode; queue.push(leftNode); } if (rightVal != '*') { const rightNode = new TreeNode(rightVal); root.right = rightNode; queue.push(rightNode); } cursor += 2; } return root; };

二叉树的最近公共祖先(Facebook 在半年内面试常考)

从前序与中序遍历序列构造二叉树(字节跳动、亚马逊、微软在半年内面试中考过)

组合(微软、亚马逊、谷歌在半年内面试中考过)

全排列(字节跳动在半年内面试常考)

全排列 II (亚马逊、字节跳动、Facebook 在半年内面试中考过)

// 二叉树的最近公共祖先 /** * @param {TreeNode} root * @param {TreeNode} p * @param {TreeNode} q * @return {TreeNode} */ var lowestCommonAncestor = function(root, p, q) { const dfs = (root, p, q) => { if (!root || root == p || root == q) return root; const left = dfs(root.left, p, q), right = dfs(root.right, p, q); if (!left) return right; if (!right) return left; return root; } return dfs(root, p, q); };
// 前序遍历与中序遍历序列构造二叉树 /** * @param {number[]} preorder * @param {number[]} inorder * @return {TreeNode} */ var buildTree = function(preorder, inorder) { const map = new Map(); for (let i = 0; i < inorder.length; i++) { map.set(inorder[i], i); } return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length, map); }; function buildTreeHelper (preorder, p_start, p_end, inorder, i_start, i_end, map) { if (p_start == p_end) { return null; } const root_val = preorder[p_start], root_idx = map.get(root_val), left_idx = root_idx - i_start; const root = new TreeNode(root_val); root.left = buildTreeHelper(preorder, p_start + 1, p_start + left_idx + 1, inorder, i_start, root_idx, map); root.right = buildTreeHelper(preorder, p_start + left_idx + 1, p_end, inorder, root_idx + 1, i_end, map); return root; }
// 组合 /** * @param {number} n * @param {number} k * @return {number[][]} */ var combine = function(n, k) { const ret = []; const dfs = (cur, n, k, temp) => { if (temp.length + (n - cur + 1) < k) return; if (temp.length == k) { return ret.push(temp); } dfs(cur + 1, n, k, [...temp, cur]); dfs(cur + 1, n, k, temp); } dfs(1, n, k, []); return ret; };
// 全排列 /** * @param {number[]} nums * @return {number[][]} */ var permute = function(nums) { const ret = []; const backtrack = (path) => { if (path.length === nums.length) { return ret.push(path); } nums.forEach(n => { if (path.includes(n)) return; backtrack(path.concat(n)); }); } backtrack([]); return ret; };
// 全排列 II /** * @param {number[]} nums * @return {number[][]} */ var permuteUnique = function(nums) { const ret = []; const visited = new Array(nums.length).fill(false); const backtrack = (idx, path) => { if (path.length === nums.length) { return ret.push(path.slice()); } for (let i = 0; i < nums.length; i++) { if (visited[i] || (i > 0 && nums[i] === nums[i - 1] && !visited[i - 1])) continue; path.push(nums[i]); visited[i] = true; backtrack(idx + 1, path); visited[i] = false; path.pop(); } } nums.sort((x, y) => x - y); backtrack(0, []); return ret; };