二叉树递归算法题(经典算法题JavaScript递归实现,持续产出中)

476 阅读3分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

二叉树的作用

理解高级数据结构的基础

graph TD
二叉树 --> 完全二叉树
二叉树 --> 多叉树/森林
二叉树 --> 二叉排序树
完全二叉树 --> 堆
完全二叉树 --> 优先队列
多叉树/森林 --> 字典树
多叉树/森林 --> AC自动机
多叉树/森林 --> 并查集
二叉排序树 --> AVL树
二叉排序树 --> 2-3树
二叉排序树 --> 红黑树
多叉排序树 --> B-树/B+树

堆、优先队列是维护集合最值的神兵利器

字典树、AC自动机是字符串及其相关转换问题的神兵利器

并查集是连通性问题的神兵利器

AVL树、2-3树、红黑树是语言标准库中重要的数据检索容器的底层实现

B-树、B+树是文件系统、数据库底层重要数据结构

练习递归技巧的最佳选择

设计/理解递归程序

  1. 数学归纳法 -> 结构归纳法
  2. 赋予递归函数一个明确的意义
  3. 思考边界条件
  4. 实现递归过程
  • k0是正确的
  • 假设ki -> ki+1
  • k0 -> k1 -> ... kn-1 -> kn
function fib(n) {  //fib(n)代表第N项斐波那契数列的值
    if (n <= 2) return n;  //k0正确
    return fib(n-1) + f(n-2);  //假设ki正确
}

左孩子右兄弟表示法节省空间

middle_img_v2_577f50e4-1464-4b30-9d0b-80571bf49edg.png

二叉树的算法(递归)

二叉树的前序遍历-144

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

image.png

输入:root = [1,null,2,3] 输出:[1,2,3] 示例 2:

输入:root = [] 输出:[]

示例 3:

输入:root = [1] 输出:[1]

示例 4: image.png

输入:root = [1,2] 输出:[1,2]

示例 5:

image.png

输入:root = [1,null,2] 输出:[1,2]

const preorder = function(root, arr) {
    if (!root) return;
    arr.push(root.val)
    preorder(root.left, arr)
    preorder(root.right, arr)
}

const preorderTraversal = function(root) {
    const arr = []
    preorder(root, arr);
    return arr;
};

N叉树的前序遍历-589

/**
 * @param {Node|null} root
 * @return {number[]}
 */
const preorder = function(root) {
    let arr = [];
    preorderFunc(root, arr)
    return arr
};


const preorderFunc = function(root, arr) {
    if (!root) return;
    arr.push(root.val);
    if (root.children) {
        root.children.forEach(item => {            
            preorderFunc(item, arr)
        })
    }
}

翻转二叉树-226

  1. 函数意义:翻转以root为根节点的二叉树
  2. 边界条件:root为空时不需要翻转
  3. 递归过程:翻转root的左子树,翻转root的右子树
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(!root) return null;
    let tempNode = invertTree(root.left);
    root.left = invertTree(root.right);
    root.right = tempNode;
    return root;
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

  1. 函数意义:将每个节点的数值放入应该的层数(需记录层数)
  2. 边界条件:root为空时不需要放值
  3. 递归过程:先放当前节点的值,再将左右节点放到下一层的数组

递归解法:

const levelOrder = function(root) {
    let arr = []
    _levelOrder(root, arr, 0)
    return arr
};

const _levelOrder = function(root, arr, i) {
    if (!root) return;
    arr[i] ? arr[i].push(root.val) : arr[i] = [root.val];
    _levelOrder(root.left, arr, i+1)
    _levelOrder(root.right, arr, i+1)
}

队列解法:

const levelOrder = function (root) {
    if (!root) return []
    const queue = [root];
    const arr = [];

    while (queue.length > 0) {
        const temp = [];
        let i = queue.length
        for (;i > 0; i--) {
            const node = queue.shift();
            temp.push(node.val)
            if (node.left) {
                queue.push(node.left)
            }
            if (node.right) {
                queue.push(node.right)
            }
        }
        arr.push(temp)
    }
    return arr
};

107. 二叉树的层序遍历 II

剑指 Offer 32 - II. 从上到下打印二叉树 II类似,只要翻转数组就行。

const levelOrderBottom = function(root) {
    const result = [];
    _levelOrderBottom(root, result, 0);
    
    // 小技巧
    for(let i=0, j = result.length - 1; i < j; i++, j--) {
        const temp = result[j];
        result[j] = result[i];
        result[i] = temp;
    }
    return result;
    //return result.reverse()
};

const _levelOrderBottom = function(root, arr, idx) {
    if (!root) return;
    arr[idx] ? arr[idx].push(root.val) : arr[idx] = [root.val];
    root.left && _levelOrderBottom(root.left, arr, idx + 1);
    root.right && _levelOrderBottom(root.right, arr, idx + 1);
}

103. 二叉树的锯齿形层序遍历

同上,只要奇数层翻转就行

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
const zigzagLevelOrder = function (root) {
    const result = [];
    _zigzagLevelOrder(root, result, 0);
    for (let i = 1; i < result.length; i += 2) {
        result[i] = result[i].reverse()
    }
    return result
};

const _zigzagLevelOrder = function (root, arr, idx) {
    if (!root) return;
    arr[idx] ? arr[idx].push(root.val) : arr[idx] = [root.val];
    root.left && _zigzagLevelOrder(root.left, arr, idx + 1);
    root.right && _zigzagLevelOrder(root.right, arr, idx + 1);
}

110. 平衡二叉树

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
const isBalanced = function(root) {
    return getHeight(root) >= 0
};

const getHeight = (root) => {
    if (!root) return 0;
    const left = getHeight(root.left);
    const right = getHeight(root.right);
    // 如果左右绝对值大于1,不平衡
    if (Math.abs(left - right) > 1) return -2;
    // 如果左子树或右子树小于0,说明有不平衡
    if (left < 0 || right < 0) return -2;
    // 返回左右子树更高值+1=当前节点高度
    return Math.max(left, right) + 1;
}

112. 路径总和

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {boolean}
 */
const hasPathSum = function(root, targetSum) {
    if (!root) return false;
    // 如果是叶子节点,判断当前值是否是目标值
    if (!root.left && !root.right) return root.val === targetSum;
    return hasPathSum(root.left, targetSum - root.val) 
    || hasPathSum(root.right, targetSum - root.val);
};

105. 从前序与中序遍历序列构造二叉树

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
const buildTree = function (preorder, inorder) {
    if (!preorder.length) return null;

    const result = new TreeNode();
    const root = preorder.shift();
    result.val = root;
    let inIdx = 0;
    while(inorder[inIdx] !== root) inIdx++;

    const leftPre = preorder.slice(0, inIdx);
    const rightPre = preorder.slice(inIdx);
    const leftIn = inorder.slice(0, inIdx);
    const rightIn = inorder.slice(inIdx + 1);

    result.left = buildTree(leftPre, leftIn);
    result.right = buildTree(rightPre, rightIn);
    return result;
};

222. 完全二叉树的节点个数

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var countNodes = function(root) {
    if (!root) return 0;
    return 1 + countNodes(root.left) + countNodes(root.right)
};

剑指 Offer 54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

注意:二叉搜索树,左边的节点一定小于右边节点。即二叉搜索树的中序遍历一定是个有序数组。

思路:先计算右节点有多少个数,判断第K大节点在哪个位置。去

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} k
 * @return {number}
 */
const kthLargest = function(root, k) {
    const r_count = countTree(root.right);
    if (k <= r_count) return kthLargest(root.right, k);
    if (k === r_count + 1) return root.val;
    return kthLargest(root.left, k - r_count - 1);
};

const countTree = (root) => {
    if (!root) return 0;
    return countTree(root.left) + countTree(root.right) + 1;
}

剑指 Offer 26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} A
 * @param {TreeNode} B
 * @return {boolean}
 */
 // 求是否是子树
const isSubStructure = function(A, B) {
    if (!B || !A) return false;
    if (B.val === A.val && is_Match(A,B)) return true;   
    return isSubStructure(A.left, B) || isSubStructure(A.right, B)
};

// 求是否节点匹配
const is_Match = (A, B) => {
    if (!B) return true;
    if (!A) return false;
    if (B.val !== A.val) return false;
    return is_Match(A.left, B.left) && is_Match(A.right, B.right);
}