js实现二叉树相关算法-leetcode整理(持续更新)

二叉树

在此之前,先来回顾一下二叉搜索树的创建和基本方法
掌握了以下代码,将能熟练地对二叉树更进一步的操作

var Node=function(key){
    this.key=key;
    this.left=null;
    this.right=null;
}
class BinaryTree{
    constructor(){
        this.root=null;
    }

    // 插入节点(按二叉搜索树要求)
    insert(key){
        let newNode=new Node(key);
        if(this.root==null){
            this.root=newNode;
        }else{
            this.insertNode(this.root,newNode);
        }
    } 
    insertNode(node,newNode){
        if(newNode.key<node.key){
            if(node.left==null){
                node.left=newNode;
            }else{
                this.insertNode(node.left,newNode);
            }
        }else{
            if(node.right==null){
                node.right=newNode;
            }else{
                this.insertNode(node.right,newNode);
            }
        }
    }


    //前序遍历
    DLR(callback){
        this.traverseNodesDLR(this.root,callback);
    }
    traverseNodesDLR(node,callback){
        if(node!=null){
            callback(node.key);
            this.traverseNodesDLR(node.left,callback);
            this.traverseNodesDLR(node.right,callback)
        }
    }

    // 中序遍历
    LDR (callback){
       this.traverseNodesLDR(this.root,callback);
     }

    traverseNodesLDR=function (node,callback){
          if(node){
             this.traverseNodesLDR(node.left,callback);
             callback(node.key);
             this.traverseNodesLDR(node.right,callback);
          }
    }

    // 查找最小值
    min(){
        let current=this.root;
        while(current.left!=null){
            current=current.left;
        }
        return current.key;
    }
    // 查找最大值
    max=function(){
        let current=this.root;
        while(current.right!=null){
            current=current.right;
        }
        return current.key;
    }

    //删除节点
    removeNode = function(node,key){
        if(node ===null){
            return null
        }
        if(key<node.key){
            node.left=this.removeNode(node.left,key);
            return node;
        }else if(key>node.key){
            node.right = this.removeNode(node.right,key);
            return node;
        }else{
        //删除节点
        //1、删除没有左右子树的节点
        if(node.left ===null && node.right ===null){
            node =null;
            return node;
        }
        //2、删除只有右子树的节点
        if(node.left === null){
           node = node.right;
           return node;
        }
        //3、删除只有左子树的节点
        if(node.right === null){
            node = node.left;
            return node;
        }
        //4、删除左右子树都有的节点

        //4.1查找右子树中最小的节点N,
        var minNode = getMinNode(node.right);
        //4.2用N替换需要删除的节点,
        node.key = minNode.key;
        //4.3删除右子树最小的节点
        node.right =this. removeNode(node.right,minNode.key);
        return node
        }
    }

     deleteNode = function(key){
        this.removeNode(this.root,key);
     }

}


var callback=item=>console.log(item);

下面的题目全都来自leetcode,直接leetcode搜名字可以找到。 属于比较常见的二叉树相关算法,难度:简单/中等
代码均为简单易于理解的版本,效率还不错

二叉树的前中后序遍历

首先要掌握二叉树的遍历方法 主要是两种方法,递归和迭代

递归很简单,面试常考迭代

  1. 前序遍历
// 1.递归 
 var preorderTraversal = function(root) {
 let res = []
 let traversal = function(root) {
 if(!root) return
 res.push(root.val)
 traversal(root.left)
 traversal(root.right)
 }
 traversal(root)
 return res
 };
 // 2.迭代    显式维护一个栈
 var preorderTraversal = (root)=>{
 let res = []
 if(!root) return res
 let stack = [root]
 while(stack.length) {
 let top = stack.pop()
 res.push(top.val)
 if(top.right) stack.push(top.right)  // 右节点先入栈
 if(top.left) stack.push(top.left)
 }
 return res
 }
  1. 中序遍历
 // 中序遍历  迭代
 var inorderTraversal = (root) => {
 let res = []
 if(!root) return res
 let stack = [root]
 while(stack.length||root) {
 let top = stack[stack.length-1]
 while(root) {
 stack.push(root)
 root = root.left
 }
 let root = stack.pop()
 res.push(root.val)
 if(root.right) stack.push(root.right)
 }
 return res
 }
​
 /// 迭代
 var inorderTraversal = function(root) {
 const res = [];
 const stk = [];
 while (root || stk.length) {
 while (root) {
 stk.push(root);
 root = root.left;
 }
 root = stk.pop();
 res.push(root.val);
 root = root.right;   // 对它中序遍历
 }
 return res;
 };
  1. 后序遍历

递归的方式很简单,跟前序中序类似,这里不再赘述

 var postorderTraversal = function(root) {
      let res = [],stack = []
        while(root||stack.length) {
            res.unshift(root.val)  // 往前插入
            if(root.left) stack.push(root.left)
            if(root.right) stack.push(root.right)
            root = stack.pop()
        }
        return res
};

700. 二叉搜索树中的搜索

var searchBST = function(root, val) {  
 if(root==null) return null;  
 if(root.val==val) return root;  
 if(root.val<val){  
 /////////这里不加return返回了undefined  
 //外层函数无法收到返回值  
 return searchBST(root.right,val)  
 }else {  
 return searchBST(root.left,val)  
 }   
};

1564 二叉搜索树转链表

思路 二叉搜索树的中序遍历时做指针移动操作

var convertBiNode = function(root){  
 if(!root)  return null  
 let p=new TreeNode('head')  //哨兵节点  
 let curr=p  //始终指向最新的节点  
 var LDR=function(node){  
 if(!node) return null  
 LDR(node.left)  
 //指针移动  
 node.left=null  //当前节点的左节点置空,不指向其他节点  
 curr.right=node  //连接  
 curr=curr.right  
 LDR(node.right)   
 }   
 LDR(root)  
 return p.right  
}

二叉搜索树与双向链表

剑指 Offer 36. 二叉搜索树与双向链表

//没有头节点的循环链表
var treeToDoublyList = function(root) {
   if(!root) return
    let head=null
    p=head      //p用于遍历
    LDR(root)
    p.right=head  //最后首尾的处理
    head.left=p
    return head

    function LDR(node){    //放到外部传参的话不能改变p
        if(!node) return 
        LDR(node.left)
        {
            if(p){
                p.right=node
            }else{  //最左边节点
                head=node
            }
            node.left=p
            p=node
        }
        LDR(node.right)
    }
};

平衡二叉树

var isBalanced = function(root) {
    if(judge(root)==-1) return false
    return true
};
function judge(root){
    if(!root){
        return 0
    }
    let left=judge(root.left)
    if(left==-1)  //该节点左孩子的左右子树不平衡,直接将该节点也返回不平衡
        return -1
    let right=judge(root.right)
    if(right==-1) return -1
    if (Math.abs(left-right)>1) return -1
    return Math.max(left,right)+1
}

102 二叉树层序遍历

关键:出队时左右孩子入队

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
  var levelOrder=function(root){
    let result=[];
    if(root==null)
      return result;
    let queue=[root];  //根节点入队
    while(queue.length>0){    
      let arr=[];  
      //////根据题目要求,每层的放在一个数组。因此还需一个循环控制 层
      let len=queue.length;
      while(len){      //遍历当前层
        let node=queue.shift();  //删除并返回首元素
        arr.push(node.val);
        if(node.left)  queue.push(node.left)
        if(node.right)  queue.push(node.right)
        len--;   ///**易漏
      }
      result.push(arr);
    }
    return result;    //每层放在一个数组中
  }

515\. 在每个树行中找最大值

层序遍历时多了一步

var largestValues = function(root) {
    if(!root) return []
    let res=[]
    let queue=[root]
    while(queue.length){
        let len=queue.length
        let tmp=[]
        while(len){
            let t=queue.shift()
            if(t.left) queue.push(t.left)
            if(t.right) queue.push(t.right)
            tmp.push(t.val)
            len--
        }
        res.push(Math.max(...tmp))
    }
    return res
};

1603 二叉树的镜像

///////////递归
var mirrorTree = function(root) {
    let change=function(node){
        if(!node){
            return
        }
        //这里加入对子节点的判断能提高效率
        //避免对子节点的两个null交换
        if(node.left||node.right){
            let temp=node.left       //用[ ]解构交换更快
            node.left=node.right
            node.right=temp
            change(node.left)
            change(node.right) 
        }   
    }
    change(root)
    return root
};

对称二叉树

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

//方法1. 递归
var isSymmetric = function(root) {
    if(!root) return true
    let isEqual=function(left,right){
        if(!left&&!right) return true  //到底了,肯定为ture,否则中途会返回false
        if(!left||!right) return false  //一空一不空
        if(left.val!=right.val) return false
        //只剩值相等的情况,继续判断
        return isEqual(left.left,right.right)&&isEqual(left.right,right.left)
    }
    return isEqual(root.left,root.right)
};


//方法2. 层序遍历
var isSymmetric = (root) => {
  if (!root) return true
  let queue = [root.left, root.right]
  while (queue.length) {          // 队列为空代表没有可入列的节点,遍历结束
    let len = queue.length        // 获取当前层的节点数
    for (let i = 0; i < len; i += 2) { // 一次循环出列两个,所以每次+2***
      let left = queue.shift()    // 左右子树分别出列
      let right = queue.shift()   // 分别赋给left和right变量
      if ((left && !right) || (!left && right)) return false // 不满足对称
      if (left && right) { // 左右子树都存在
        if (left.val !== right.val) return false // 左右子树的根节点值不同
        queue.push(left.left, right.right) // 让左子树的left和右子树的right入列
        queue.push(left.right, right.left) // 让左子树的right和右子树的left入列
      }
    }
  }
  return true // 循环结束也没有遇到返回false
}

104\. 二叉树的最大深度

var maxDepth = function(root) {
    if(!root) return 0
    let right=maxDepth(root.right)
    let left=maxDepth(root.left)
    return Math.max(left,right)+1
};

N叉树的最大深度

深度优先和广度优点

  1. 深度优先 递归求出每个子树的最大深度,再加上根节点
var maxDepth = function(root) {  
 if(!root) return 0;  
 if(!root.children) return 1;  
 let max = 0;  
 for(let i=0; i<root.children.length; i++) {  
 let childDepth = maxDepth(root.children\[i\]);  
 max = Math.max(max, childDepth)  
 }  
 return max + 1;  
};

2.广度优先 算出总共有几层即可

// 层序遍历,迭代  
var maxDepth = function(root) {  
 if(!root) return 0;  
 let queue = \[\];  
 let level = 0;  
 queue.push(root);  
​  
while(queue.length) {  
 let length = queue.length;  
 while(length -- ) {  
 let node = queue.shift();  
 node.children && (queue = queue.concat(node.children));  
 }  
 level ++;  
}  
return level;  
};

111\. 二叉树的最小深度

var minDepth = function(root) {
    if(!root) return 0;
    let left=minDepth(root.left);
    let right=minDepth(root.right);
    if(!root.left||!root.right) return left+right+1;
    return Math.min(left,right)+1;  //左右均不为空
};

面试题34. 二叉树中和为某一值的路径

var pathSum = function(root, sum) {
    let res = []
    let dfs=function(root, path, sum){
        if(!root) return
        sum -= root.val  
        if(sum == 0 && !root.left && !root.right){
            res.push([...path, root.val])
            return
        }
    path.push(root.val)
    dfs(root.left, path, sum)
    dfs(root.right, path, sum)
    path.pop()
}
    dfs(root, [], sum)
    return res
};

两颗二叉树合并

都有节点时求和

var mergeTrees = function(t1, t2) {
    if(!t2) return t1  //t2空。t1是否空不重要
    if(!t1) return t2  //t1空,t2不空
    if(t1&&t2){
        t1.val+=t2.val
    }
   t1.left=mergeTrees(t1.left,t2.left)
   t1.right=mergeTrees(t1.right,t2.right)
    return t1   //新树用t1返回
};

数组转二叉排序树(平衡二叉树)

var sortedArrayToBST = function(nums){
    if(nums.length==0){
        return null
    }
    let m=Math.floor((nums.length-1)/2)
    let p=new TreeNode(nums[m])
    p.left=sortedArrayToBST(nums.slice(0,m))
    p.right=sortedArrayToBST(nums.slice(m+1))
    return p
};

二叉树公共祖先

(注意是二叉搜索树,容易确定在哪个子树上)

  1. 从根节点开始遍历树
  2. 如果节点p和节点q都在右子树上,那么以右孩子为根节点继续 1 的操作
  3. 如果节点p 和节点q都在左子树上,那么以左孩子为根节点继续 1 的操作
  4. 如果条件 2 和条件 3 都不成立,已经找到
  • 临界条件:

    • 根节点是空节点
    • 根节点是q节点
    • 根节点是p节点
  • 从左右子树分别进行递归,即查找左右子树上是否有p节点或者q节点

    • 左右子树均无p节点或q节点
    • 左子树找到,右子树没有找到,返回左子树的查找结果
    • 右子树找到,左子树没有找到,返回右子树的查找结果
    • 左、右子树均能找到,说明此时的p节点和q节点在当前root节点两侧,返回root节点
var lowestCommonAncestor = function(root, p, q) {
    if(!root||root===p||root===q)
        return root;
    let left = lowestCommonAncestor(root.left,p,q);
    let right = lowestCommonAncestor(root.right,p,q);
    if(left&& right)  //分别在root的左右两边
        return root;
    return left!= null ? left : right;  //都在左子树或都在右子树上
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

//分治  每次判断左子树所有节点均小于当前根节点,右子树均大于当前根节点
//
var verifyPostorder = function (postorder){
    let len=postorder.length
    if(len<3) return true   //长度0,1,2时,一定能构造出来满足条件的树
    root=postorder[len-1]

    //划分,找第一个比根节点大的
    let i=0;
    for(;i<len-1;i++){
        if(postorder[i]>root) break
    }
    //右子树是否满足
    let flag=true
    for(let j=i+1;j<len;j++){
        if(postorder[j]<root) flag = false
    }
    ////注意这里的区间
    if(flag) return verifyPostorder(postorder.slice(0,i))&&verifyPostorder(postorder.slice(i,len-1))
    (else) return false
}

剑指 Offer 07. 重建二叉树

var buildTree = function(preorder, inorder) {
    if(!preorder.length) return null  //上个节点为叶节点,左右为null
    let node=new TreeNode(preorder[0]),i=0
    for(;i<inorder.length;i++){
        if(inorder[i]===node.val) break
    }
    node.left=buildTree(preorder.slice(1,1+i),inorder.slice(0,i))
    node.right=buildTree(preorder.slice(1+i),inorder.slice(i+1))
    return node
};

剑指 Offer 26. 树的子结构

题意:判断A是不是B的子树

var isSubStructure = function(A, B) {
    if(!A||!B) return false
    if(A.val===B.val&& find(A.left,B.left)&&find(A.right,B.right)) return true  //根节点相同并且左右子树都相同(find递归)
    else{
        return isSubStructure(A.left,B)||isSubStructure(A.right,B)
    }
};
let find=function(A,B){
     if(!B) return true
    if(!A) return false
    if(A.val!==B.val){
        return false
    }
    return find(A.left,B.left)&&find(A.right,B.right)
}
阅读 953

推荐阅读