刷leetcode基础——二叉树

569 阅读3分钟

基础

概念

二叉树是一种 每个节点最多有两个子树 的树状数据结构。

常见的遍历顺序

  • 深度遍历

    前序:中→左→右

    中序:左→中→右

    后序:左→右→中(删除二叉树时会使用的遍历)

  • 广度遍历

    层序遍历(也可以使用深度遍历的思想)

遍历解析

首先定义树形结构,后面的遍历函数都是以此为基础的输入

/**
 * 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 preorderTraversal = function(root) {
    let result = []
    var preOrderTraverseNode = (node) => {
        if(node) {
            // 先根节点
            result.push(node.val)
            // 然后遍历左子树
            preOrderTraverseNode(node.left)
            // 再遍历右子树
            preOrderTraverseNode(node.right)
        }
    }
    preOrderTraverseNode(root)
    return result;
};

非递归版本

// 利用栈辅助 
const preOrderTraverseUnRecur = (root) => {
    let res = [];
    let stack = [root];
    let node = null;

    while(stack.length !== 0) {
        node = stack.pop();
        // 第一步的时候,先访问的是根节点
        res.push(node.val);

        if(node.right) {
            stack.push(node.right)
        }

        // 因为pop是取出最后一个元素,所以我们要确保首先拿到的是左节点
        if(node.left) {
            stack.push(node.left)
        }
    }
		return res;
}

中序遍历

递归版本

var inorderTraversal = function(root) {
    let result = []
    var preOrderTraverseNode = (node) => {
        if(node) {
            preOrderTraverseNode(node.left);
            result.push(node.val);
            preOrderTraverseNode(node.right);
        }
    }
    preOrderTraverseNode(root)
    return result;
};

非递归版本

function inorderTraversal (root) {
    let res = [];
    let stack = [];
    while(stack.length > 0 || root){
        if(root){
            stack.push(root);
            root = root.left;
        } else {
            root = stack.pop();
            if(root.val) res.push(root.val);
            root = root.right;
        }
    }
    return res;
}

后序遍历

递归版本

var inorderTraversal = function(root) {
    let result = []
    var preOrderTraverseNode = (node) => {
        if(node) {
            preOrderTraverseNode(node.left)
            preOrderTraverseNode(node.right)
            result.push(node.val)
        }
    }
    preOrderTraverseNode(root)
    return result;
};

非递归版本

var postorderTraversal = function(root) {
    let res = [];
    let stack = [];
    if(root) stack.push(root);
    while(stack.length){
        root = stack.pop();
        if(root.val) res.unshift(root.val);
        if(root.left) stack.push(root.left);
	if(root.right) stack.push(root.right);
    }
    return res;
};

// 解题思路: 后序遍历与前序遍历不同的是:
// 后序遍历是左右根
// 而前序遍历是根左右
// 如果我们把前序遍历的 list.push(node.val) 变更为 list.unshift(node.val) (遍历结果逆序),那么遍历顺序就由 根左右 变更为 右左根
// 然后我们仅需将 右左根 变更为 左右根 即可完成后序遍

应用

常见考察题目

二叉树最大深度

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

思路:

  • 深度遍历
    记录和比较每条路径的深度,取最大值
// 深度遍历,比较每条路径
var maxDepth = function(root) {
    if(!root) return 0;
    let maxDep = 0;
    const deep = function(root, n){
        if(root.val) n++;
        maxDep = Math.max(maxDep, n);
        if(root.left) deep(root.left, n);
        if(root.right) deep(root.right, n);
    }
    deep(root, 0);
    return maxDep;
};

  • 广度遍历
    由上自下,记录层数,直到没有下一层节点
// 广度遍历,记录每层的树节点
var maxDepth = function(root) {
    if(!root) return 0;
    let maxDep = 0;
    let queue = [root];
    while(queue.length){
        maxDep++;  // 层数
        let curQue = []; // 记录下一层遍历的根节点
        while(queue.length){
            let node = queue.shift();
            if(node.left) curQue.push(node.left);
            if(node.right) curQue.push(node.right);
        }
        queue = curQue;  
    }
    return maxDep;
};

对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

思路:

  • 递归
    比较 左子树的子树和右子树的子树 & 左子树的子树和右子树的子树(看清楚哦)
// 递归
var isSymmetric = function(root) {
    if (root === null) return true;
    const isMirror = function(left, right){
        if(left === null && right === null) return true;
        if(left === null || right === null) return false;
        return (left.val === right.val) && isMirror(left.left, right.right) && isMirror(left.right, right.left);
    }
    return isMirror(root.left, root.right);
};
  • 迭代
    记录每层的节点数据,比较reverse之后的节点数组和原始数组,一致则说明对称。 注意: 记得处理null的情况。
// 迭代
var isSymmetric = function(root) {
    if (root == null) return true
    let queue = [root]
    while(queue.length){
        let curArr = [];
        let nextQue = [];
        while(queue.length){
            let node = queue.shift();
            if(node === null){
                curArr.push('null');
            } else {
                curArr.push(node.val);
                nextQue.push(node.left);
                nextQue.push(node.right);
            }
        }
        if(Array.from(curArr).reverse().toString() !== curArr.toString()) return false;
        queue = nextQue;
    }
    return true;
};

路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum

叶子节点 是指没有子节点的节点。

var hasPathSum = function(root, targetSum) {
    if(root === null) return false;
    // 判断暂停条件-路径走完,到叶子节点啦
    if(root.val === targetSum && root.left == null && root.right == null) return true;  
    targetSum = targetSum - root.val;
    return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
};