【前端算法系列】树

104. 二叉树的最大深度

思路:在深度优先遍历过程中,记录每个节点所在层级,找出最大层级即可

1)新建一个变量,记录最大深度
2)深度优先遍历整颗树,并记录每个节点的层级,同时不断刷新最大深度这个变量
3)遍历结束返回最大深度这个变量

/** 时间复杂度:O(n) n是树的结点
	空间复杂度:函数调用函数会形成堆栈 O(logN)
*/
var maxDepth = function(root) {
    let res = 0
    const dfs = (n, l) =>{
        if(!n) return
        if(!n.left&&!n.right){ // 叶子结点再去刷新,不用每个结点都去刷新
             res = Math.max(res, l) // 不断刷新最大深度这个变量
        }
        dfs(n.left, l+1)
        dfs(n.right, l+1)
    }
    dfs(root, 1)
    return res
};

111. 二叉树的最小深度

思路:在广度优先遍历中,遇到叶子节点,停止遍历,返回节点层级

/**时间复杂度:O(n) n是树的结点
	空间复杂度:O(n) 有队列,最坏情况下队列会装满树的结点
*/
var minDepth = function(root) {
    if(!root) return 0 
    const q = [[root, 1]] // 改成嵌套数组,第二个放层级
    while(q.length){
        const [n, l] = q.shift()
        console.log(n.val, l)
        if(!n.left && !n.right) return l // 左右节点为空,即叶子节点,返回层级
        if(n.left) q.push([n.left, l+1])
        if(n.right) q.push([n.right, l+1])
    }
};

102. 二叉树的层序遍历

const levelOrder = function(root) {
    const res = []  
    // 处理边界条件
    if(!root) return res
    // 初始化队列, 第一个元素是根结点
    const queue = [root]   
    // 当队列不为空时,反复执行以下逻辑
    while(queue.length) {
        const level = []    // level用来存储当前层的结点
        // 缓存刚进入循环时的队列长度,这一步很关键,因为队列长度后面会发生改变
        const len = queue.length  
        // 循环遍历当前层级的结点
        for(let i=0;i<len;i++) {
            const top = queue.shift()  // 取出队列的头部元素
            level.push(top.val) // 将头部元素的值推入level数组
            // 如果当前结点有左或右孩子,则推入下一层级
            if(top.left)  queue.push(top.left)
            if(top.right)  queue.push(top.right)
        }
        res.push(level) // 将level推入结果数组
    }
    return res
}

优化:不用额外开辟空间存当前层

/**时间复杂度:O(n) n是树的结点
	空间复杂度:O(n) 有队列,最坏情况下队列会装满树的结点
*/
var levelOrder = function(root) {
    if(!root) return []
    const q = [root]
    const res = []
    while(q.length){
        let len = q.length
        res.push([])
        while(len--){
            const n = q.shift()
            res[res.length-1].push(n.val)
            if(n.left) q.push(n.left)
            if(n.right) q.push(n.right)
        }
    }
    return res
};

94. 二叉树的中序遍历

// 递归
var inorderTraversal = function(root) {
    const res=[]
    const rec = (n)=>{
        if(!n) return
        rec(n.left)
        res.push(n.val)
        rec(n.right)
    }
    rec(root)
    return res
};
// 迭代
var inorderTraversal = function(root) {
  const res=[]
  const stack=[]
  let p = root
  while(stack.length || p){
    while(p){
        stack.push(p)
        p = p.left
    }
    const n = stack.pop()
    res.push(n.val)
    p = n.right
  }
  return res
};

112. 路径总和

var hasPathSum = function(root, sum) {
    if(!root) return false
    let res=false
    const dfs=(n, s)=>{
        if(!n.left && !n.right && s===sum) res = true
        if(n.left) dfs(n.left, n.left.val+s)
        if(n.right) dfs(n.right, n.right.val+s)
    }
    dfs(root, root.val)
    return res
};

226.翻转二叉树

思路:以递归的方式,遍历树中的每一个结点,并将每一个结点的左右孩子进行交换

const invertTree = function(root) {
    // 定义递归边界
    if(!root) return root
    // 不断交换左右孩子节点
    root.left = invertTree(root.right)
    root.right = invertTree(root.left) 
    return root
};

对称二叉树

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

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    1
   / \
  2   2
   \   \
   3    3

在这里插入图片描述

// 二叉树结点
class Node {
    constructor(val) {
        this.val = val
        this.left = this.right = undefined
    }
}
class Tree {
    // 创建二叉树
    // 思路:把node关联到父结点的左结点或右结点,所以要找父结点。
    constructor(data) {
        // 临时存储所有节点,方便寻找父子节点
        let nodeList = []
        // 顶节点
        let root
        // 拿到数组的每个元素,插入到树里
        for (let i = 0, len = data.length; i < len; i++) {
            let node = new Node(data[i])
            nodeList.push(node)
            // i=0为顶点
            if (i > 0) {
                // 计算当前结点属于哪一层   拿5来测试
                let n = Math.floor(Math.sqrt(i + 1))
                // 记录当前层的起始点      比如3
                let q = Math.pow(2, n) - 1
                // 记录上一层的起始点       比如1
                let p = Math.pow(2, n - 1) - 1
                // 找到当前节点的父节点   (5-3)/2 = 1    1+1
                let parent = nodeList[p + Math.floor((i - q) / 2)]
                // 当前结点和上一层的父节点做关联
                if (parent.left) {
                    parent.right = node
                } else {
                    parent.left = node
                }
            }
        }
        root = nodeList.shift()
        nodeList.length = 0
        return root
    }
    // 检测是否对称结点
    // 思路:不断递归结点,判断父结点的左子树的左结点和右子树的右结点相同,左子树的右结点和右子树的左结点相同
    static isSymmetry(root) {
        // 没有顶点,就是空的二叉树,直接返回true
        if (!root)  return true
        let walk = (left, right) => {
            // 一起遍历完,没有了,说明通过,返回true
            if (!left && !right) {
                return true
            }
            if ((left && !right) || (!left && right) || (left.val !== right.val)) {
                return false
            }
            return walk(left.left, right.right) && walk(left.right, right.left)
        }
        return walk(root.left, root.right)
    }
}
const root = new Tree([1,2,2,3,4,4,3])
console.log(Tree.isSymmetry(root))

重建二叉树

function TreeNode(val) {
    this.val = val;
    this.left = this.right = null;
}

/*
    1)前序的第一个是树的顶点,用这个顶点在中序找出对应的左边和右边
    2)找到中序的对应左边右边后,递归构建左边树和右边树
    3)返回整棵树
*/
var buildTree = function (preorder, inorder) {
    if(!preorder.length || !inorder.length) return null
    const rootVal = preorder[0];
    const node = new TreeNode(rootVal);
    let i = 0; // i有两个含义,一个是根节点在中序遍历结果中的下标,另一个是当前左子树的节点个数
    for (; i < inorder.length; ++i) {
        if (inorder[i] === rootVal) { // 说明找到中序了
            break;
        }
    }
    // slice(从什么开始,到什么结束且不包括结束位置)   前序的1位置(下标0已经是根节点) , 中序的左侧位置
    node.left = buildTree(preorder.slice(1, i + 1), inorder.slice(0, i));
    node.right = buildTree(preorder.slice(i + 1), inorder.slice(i + 1));
    return node;
};

let preorder = [3, 9, 20, 15, 7]
let inorder = [9, 3, 15, 20, 7]
console.log(buildTree(preorder, inorder))

从上到下打印二叉树 (广度搜索)

层次遍历,用广度优先搜索,可以用队列实现

var levelOrder = function(root) {
    let res=[]
    let queue=[]
    if(root!==null) queue.push(root) // root {val:xx,left:xx,right:xx}
    while(queue.length){
        let node=queue.shift()
        res.push(node.val)
        node.left && queue.push(node.left)
        node.right && queue.push(node.right)
    }
    return res
};

从上到下打印二叉树 II (广度搜索)

var levelOrder = function(root) {
    let res=[]
    let queue=[]
    if(root!==null) queue.push(root) 
    let level=0
    while(queue.length){
        res[level]=[]
        let leveNum = queue.length
        while(leveNum--){
            let node = queue.shift()
            res[level].push(node.val)
            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
        }
         level++
    }
    return res
};

从上到下打印二叉树 III (广度搜索)

判断是否奇偶数,用reverse反转二维数组中level是偶数的那层

var levelOrder = function(root) {
    let res=[]
    let queue=[]
    if(root!=null) queue.push(root)
    let level=0
    while(queue.length){
        res[level]=[]
        let levelNum = queue.length
        while(levelNum--){
            let node = queue.shift()
            res[level].push(node.val)
            node.left && queue.push(node.left)
            node.right && queue.push(node.right)
        }
        // 行号是偶数时,翻转当前层的遍历结果
        if (level % 2) {
            res[level].reverse();
        }
        level++
    } 
    return res
};

二叉搜索树的后序遍历序列

  • 二叉搜索树特点:左节点 < 根节点 < 右节点 ;可为空树
  • 后序遍历:最后一个元素是根元素
function verifyPostorder(nums) {
    const len = nums.length
    if (len < 2) return true // 空树也可以二叉搜索树

    const root = nums[len - 1] // 最后一个元素是根节点

    // nums[0, root) nums[root, len - 1)是右子树
    let cut = 0 // 左子树节点的数量
    for (; cut < len - 1 && nums[cut] < root; ++cut) {}
    console.log(cut)  // 输出3,即下标3开始为右节点

    // 检查右子树中的值是否满足条件,如果根节点比右节点大,则返回false
    for (let i = cut; i < len - 1; ++i) {
        if (nums[i] < root) return false
    }

    // 再分别递归检查左右子树是否满足后序遍历顺序
    return (
        verifyPostorder(nums.slice(0, cut)) &&
        verifyPostorder(nums.slice(cut, len - 1))
    );
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值