二叉树算法题

  • 二叉树层次遍历

//思路

特殊情况 ,根节点为null,深度为0,return 0
借助队列,队列储存每一层的所有节点,
先保存数组长度,用于控制依次遍历循环——遍历每一个节点保存值,该节点的所有子结点从队尾入队,访问过的节点从头出队
注意——需要先把width存下来,用于for循环的结束标志,因为for循环里面直接操作了queue,不能去都不敢动态获取
队列初始值为root,对每一层都要进行上述遍历——while循环控制,队列空代表叶节点这一层遍历完成,此时遍历结束,退出循环
每次循环开始前初始化一个curNodes储存该层所有节点,每次循环结束,将curNodes压入result

var levelOrder = function(root) {
  if (root === null) return []; //空树
  var result = [],
    queue = [root];
  while (queue.length) {
    let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组
    let curNodes = [];
    for (let i = 0; i < width; i++) {
      let node = queue.shift();
      curNodes.push(node.val);
      node.left ? queue.push(node.left) : "";
      node.right ? queue.push(node.right) : "";
    }
    result.push(curNodes);
  }
  return result;
};
  • 二叉树反向层次遍历

var levelOrderBottom = function(root) {
  if (root === null) return []; //空树
  var result = [],
    queue = [root];
  while (queue.length) {
    let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组
    let curNodes = [];
    for (let i = 0; i < width; i++) {
      let node = queue.shift();
      curNodes.push(node.val);
      node.left ? queue.push(node.left) : "";
      node.right ? queue.push(node.right) : "";
    }
    result.unshift(curNodes);//从头部插入,先插入顶部的,后插入底部的额
  }
  return result;
};
  • 先序遍历

//思路
和中序其他都相似,要先访问根节点要后访问左节点,所以在根节点入栈的时候加入result数组

特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。将遍历到的结点值存入result数组,结点存入栈中。
stack存放遍历过的根,左节点,以便回溯访问右节点
然后指针一直向下访问左节点
当根节点没有左节点时,将根节点退栈,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果

var preorderTraversal = function(root) {
  if (root === null) return []; //空树
  var result = [],
    stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
  var p = root; //p指向当前遍历的节点
  //当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环
  while (stack.length != 0 || p != null) {
    //还有左节点没有访问(p不空)
    if (p != null) {
      result.push(p.val); //遍历根节点
      stack.push(p); //把遍历过的节点放入stack保管
      p = p.left; //访问左节点
    }
    //左节点访问完
    else {
      p = stack.pop().right; //栈顶节点退栈,访问右节点
    }
  }
  return result;
}; 
  • 中序遍历

//思路
和前序其他都相似,左节点要先访问根节点要后访问,所以在根节点出栈的时候加入result数组

特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。
stack存放遍历过的根,左节点,以便回溯访问右节点
然后指针一直向下访问左节点
当根节点没有左节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果

var inorderTraversal = function(root) {
  if (root === null) return []; //空树
  var result = [],
    stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
  var p = root; //p指向当前遍历的节点
  //当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环
  while (stack.length != 0 || p != null) {
    //还有左节点没有访问(p不空)
    if (p != null) {
      stack.push(p); //把遍历过的节点放入stack保管
      p = p.left; //访问左节点
    }
    //左节点访问完
    else {
        let node = stack.pop();//栈顶节点退栈,
      result.push(node.val); //遍历左节点-根节点
      p = node.right; //访问右节点
    }
  }
  return result;
};
  • 后序遍历

//思路
特殊情况:根节点为空,返回空数组
利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。
stack存放遍历过的根,右节点,以便回溯访问左节点
然后指针一直向下访问右节点
当根节点没有右节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点
直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果

var postorderTraversal = function(root) {
  if (root === null) return []; //空树
  var result = [],
    stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点
  var p = root; //p指向当前遍历的节点
  //当有未访问的左节点(stack不空)||还有右节点没有访问(p不空)时进入循环
  while (stack.length != 0 || p != null) {
    //还有左节点没有访问(p不空)
    if (p != null) {
      stack.push(p); //把遍历过的节点放入stack保管
      result.unshift(p.val); //从result数组头部插入根节点-右节点-左节点
      //最后result顺序为左节点-右节点-根节点
      p = p.right; //访问右节点
    }
    //右节点访问完
    else {
      p = stack.pop().left; //栈顶节点退栈,访问左节点
    }
  }
  return result;
};
  • 重建二叉树

//思路:二叉树前序遍历第一个点为根节点,中序遍历顺序为先左子树然后根节点最后右子树。
所以先通过前序遍历找出根节点,然后将中序遍历分为左右子树两组,最后对于每个子树依次递归调用。
function reConstructBinaryTree(pre, vin) { if (pre.length === 0 || vin.length === 0) return null; // 前序/中序又一个为空,就返回空值 let root = new TreeNode(pre[0]); //新建节点,作为根节点 if (pre.length === 1) return root; //是叶节点直接返回root,不需要计算子树 //不是叶节点,先递归得到左右节点 let rootIdx = vin.indexOf(pre[0]); //根节点在中序的位置 root.left = reConstructBinaryTree( // 递归调用得到左子树的根节点 pre.slice(1, rootIdx + 1), vin.slice(0, rootIdx) ); root.right = reConstructBinaryTree( // 递归调用得到右子树的根节点 pre.slice(rootIdx + 1), vin.slice(rootIdx + 1) ); return root; }
  • 二叉树镜像

//思路:先将根的左右节点互换,然后就是递归调用,对左右子树进行分别处理

function Mirror(root)
{
    // write code here
    if(root==null) return null;
    //首先先将左右节点互换
    var  tmp = root.left;
    root.left=root.right;
    root.right=tmp;
    //递归
    Mirror(root.left);
    Mirror(root.right);

}
  • 平衡二叉树

//在这里,由于我们是以递归的形式,所以,我们会先遍历左节点,再遍历右节点,最后再遍历根节点(后序遍历)

同时,我们判断左右树的高度差是否超过 1,如果是,则进行中断,返回 false;否则,继续递归。

最后,我们将遍历的结果与 -1 进行比较,返回 true 或者 false

let root = {
  val: 3,
  left: { val: 9, left: null, right: null },
  right: {
    val: 20,
    left: { val: 3, left: null, right: null },
    right: {
      val: 7,
      left: null,
      right: { val: 2, left: null, right: null },
    },
  },
}

var isBalanced = function(root) {
  let ergodic = function(root) {
    if (!root) {
      return 0;
    }
    let left = ergodic(root.left);
    if (left === -1) {
      return -1;
    }
    let right = ergodic(root.right);
    if (right === -1) {
      return -1;
    }
    return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
  }
  return ergodic(root) != -1;
};

console.log(isBalanced(root));
  • 二叉树深度

//题目:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

//思路
特殊情况 ,根节点为null,深度为0,return 0
递归法,递归法获取左右子树的深度,
root的深度是左右子树的深度最大值+1,返回深度

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function TreeDepth(pRoot) {
  // write code here
  if (!pRoot) return 0; //根节点为空,深度为0
  var left = TreeDepth(pRoot.left); //左深度等于左子树深度+1
  var right = TreeDepth(pRoot.right); //右深度等于右子树深度+1
  return 1 + Math.max(left, right); //该节点深度为左右深度中的max,返回
}
  • 二叉树最大深度

//思路
我们判断 root 是否还存在 left 和 right:如果不存在,那么树到了最底层,终止递归;如果存在,那么 depth 深度 + 1,并且遍历它的左树和右树。

同时,我们在树存在 left 和 right 的时候,将 depth 和 longest 比较,把最深的树给记录下来。

返回最长深度 longest。

var maxDepth = function(root) {
  return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
  • 二叉树最小深度

//思路
假设我是一只蜘蛛,我在一颗大树最底下(根节点),开始往上爬。

每经过 1 米(1 个 val 节点),我就留下一个分身。

当我爬到最顶的时候,我就进行最后标记,并告诉分身,前面凉凉了,开始报数!

于是从我为 1 开始,一直到根节点的长度,就是这个分支的高度。

const root = {
  val: 3,
  left: { val: 9, left: null, right: null },
  right: {
    val: 20,
    left: { val: 15, left: null, right: null },
    right: { val: 7, left: null, right: null },
  },
}
var minDepth = function(root) {
  if (!root) {
    return 0;
  }
  if (!root.left) {
    return minDepth(root.right) + 1;
  }
  if (!root.right) {
    return minDepth(root.left) + 1;
  }
  return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
};
minDepth(root);
  • 翻转二叉树

var invertTree = function(root) {
  if (!root) {
    return null;
  }
  return {
    val: root.val,
    left: invertTree(root.right) || null,
    right: invertTree(root.left) || null,
  }
}; 
  • 二叉树下一个节点

//题目
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

//思路
特殊情况 空节点

情况一,有右子节点,下一个访问右子树中最左边的第一个节点。通过while循环,寻找右子树的左叶子节点可以找到。

情况二,没有右子结点,有父节点,当前节点是父节点的左节点,下一个访问父节点

情况三,没有右子结点,有父节点,当前节点是父节点的右节点,此时父节点一下都已经访问完,下一个访问父节点的父节点中,满足从左边连接的第一个

情况二和情况三可以归为一类——父节点存在时,设一个指针cur指向pnode父节点,

如果pnode是cur的左节点,返回;
如果pnode是cur的右节点,pnode向上指,
如果退出循环,查找到根节点都没有返回,就表示没有满足条件的节点,返回null

function GetNext(pNode) {
  // write code here
  if (!pNode) return null; //空节点
  var cur = null;
  //有右节点,下一个访问右子树里的最左边节点
  if (pNode.right) {
    cur = pNode.right;
    while (cur.left) {
      cur = cur.left;
    }
    return cur;
  }
  //没有右节点,有父节点,
  while (pNode.next) {
    cur = pNode.next; //cur指向父节点
    //如果pnode是cur的左节点,下一个访问的就是cur
    if (cur.left === pNode) return cur;
    //如果pnode不是cur的左节点,pnode指向父节点,向上继续查找
    pNode = cur;
  }
  return null; //查找到根节点也没有符合条件的节点
  • 最大二叉树

//题目:
给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:
二叉树的根是数组中的最大元素。
左子树是通过数组中最大值左边部分构造出的最大二叉树。
右子树是通过数组中最大值右边部分构造出的最大二叉树。
通过给定的数组构建最大二叉树,并且输出这个树的根节点。

//思路
递归法实现构建二叉树
递归出口,数组长度为0,return null,返回空节点
根节点为传入数组的max值,
左节点为递归调用,传入以max为分割点的左边数组的返回值,
右节点为递归调用,传入以max为分割点的右边数组的返回值,
slice(left,right)分割数组,索引为left的元素划分进去,索引为right的元素不划分进去。

var constructMaximumBinaryTree = function(nums) {
  if (nums.length <= 0) return null;
  let max = Math.max(...nums);
  let i = nums.indexOf(max);
  let root = new TreeNode(max);
  root.left = constructMaximumBinaryTree(nums.slice(0, i));
  root.right = constructMaximumBinaryTree(nums.slice(i + 1));
  return root;
};
  • 序列化二叉树

//题目描述
请实现两个函数,分别用来序列化和反序列化二叉树

//思路:

序列化,将节点值存入数组中,空节点则使用特殊标记存入数组中。
反序列化,从数组中获取元素,为number类型则生成节点,为特殊标记,则为空节点

var arr=[];
function Serialize(pRoot)
{
    // write code here
    if(pRoot==null){
        arr.push('#')
        return;
    } 
    arr.push(pRoot.val);
    Serialize(pRoot.left)
    Serialize(pRoot.right)
}
function Deserialize(s)
{
    // write code here
    if(arr==null){
        return null;
    }

    if(arr.length<1){
        return null;
    }
    var root=null;
    var temp=arr.shift();
    if(typeof temp=='number'){
        root=new TreeNode(temp);
        root.left=Deserialize(arr);
        root.right=Deserialize(arr);
    }
    return root;
}
  • 二叉树中和为某一值的路径

//思路

因为要先访问最长的路径,所以考虑`
递归深度遍历:对根节点调用递归函数
curPath保存当前路径,sum保存当前和,每访问一个节点,更新路径和和,
更新之后,判断是否满足叶节点+和为指定值,如果满足,压入结果数组
注意:因为 curPath是一个引用类型,它的值一直在变化,所以压入数组时需要先进行深拷贝,如果不拷贝会导致结果输出为最后的curPath空数组。
左右节点存在时,对左右节点递归遍历
当前节点和所有子节点遍历完成,当前节点退出路径


function FindPath(root, expectNumber) {
  // write code here
  if (!root) return [];
  var res = [], //所有满足条件路径
    curPath = [], //当前路径
    p = root, //访问指针
    sum = 0; //当前路径和
  dfs(p, curPath, sum, res, (exp = expectNumber));
  return res;
}
function dfs(p, curPath, sum, res, exp) {
    if(!p&&curPath.length===0) return;//所有节点访问完毕,退出递归
  curPath.push(p.val); //节点值加入路径
  sum += p.val; //更新和
  if (!p.left && !p.right && sum === exp) {
    //栈顶节点是叶节点且路径满足条件
    res.push(curPath.slice(0)); //路径加入结果
  }
  p.left ? dfs(p.left, curPath, sum, res, exp) : "";
  p.right ? dfs(p.right, curPath, sum, res, exp) : "";
  curPath.pop(); //节点和所有子树访问完毕,退出路径
}
  • 求根到叶子节点数字之和

//题目:
给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。计算从根到叶子节点生成的所有数字之和。 //方法一:递归法 //思路 递归输入当前节点和上一层的和,初始为root和0 如果节点空返回0 更新sum,是叶节点的话直接返回这个值 不是叶节点再加上左右节点的递归返回值 var sumNumbers = function(root) { return dfs(root,0); }; var dfs = function(p,sum) { if(!p) return 0; sum=sum*10+p.val; if(!p.left&&!p.right) return sum; return dfs(p.left,sum)+dfs(p.right,sum); };
  • 左叶子之和

const sumOfLeftLeaves = (root, left) => {
  if (!root) {
    return 0;
  }
  if (!root.left && !root.right && left) {
    return root.val;
  }
  return sumOfLeftLeaves(root.left, true) + sumOfLeftLeaves(root.right);
};
  • 二叉树所有路径

//思路:
使用递归方法来解决此问题。对于根节点,如果根节点为空,则返回空数组[],如果为叶子节点,则返回包含此节点的值的数组(这个数组只有一个元素)。
若不为空也不是叶子节点,则对左右子树递归调用,将两个结果数组拼接起来,最后使用 map 函数来对数组的每个元素进行字符串的操作。
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ var binaryTreePaths = function(root) { if (root === null) return []; if (root.left === null && root.right === null) { return [root.val.toString()]; } var left = binaryTreePaths(root.left), right = binaryTreePaths(root.right); return left.concat(right).map(x => root.val + '->' + x); };
  • 树的子结构

//题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

//思路:比较B是不是A的子树,B是不是A的右子树的子树,B是不是A的左子树的子树。如果根元素相同,则开始判断左子树和右子树

function isSubtree(pRoot1,pRoot2){
    if (pRoot2 == null) return true;//pRoot2为null,表示子树已经遍历完
    if (pRoot1 == null) return false;
    if(pRoot1.val==pRoot2.val){
        return isSubtree(pRoot1.left,pRoot2.left) && isSubtree(pRoot1.right,pRoot2.right);
    }else{
        return false;
    }
}

function HasSubtree(pRoot1, pRoot2)
{
    // write code here
    if(pRoot1==null||pRoot2==null) return false;   
    return isSubtree(pRoot1,pRoot2)||HasSubtree(pRoot1.left,pRoot2)||HasSubtree(pRoot1.right,pRoot2);
} 
  • 二叉搜索树最近公共祖先

//思路:
首先确保p的值小于q,若不是,则互换。这样有助于判断root、p、q三者的位置。

三种情况

root最小,说明p和q的最近公共祖先一个在root的右边,使用root.right递归调用
root在中间,说明最近公共祖先只能是root,返回root
root最大,说明p和q的最近公共祖先一个在root的左边,使用root.left递归调用

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

var lowestCommonAncestor = function(root, p, q) {
  if (p.val > q.val) [p, q] = [q, p];                           // 让p小于q,方便判断
  if (root.val >= p.val && root.val <= q.val) {
    return root;
  } else if (root.val <= p.val && root.val <= q.val) {
    return lowestCommonAncestor(root.right, p, q);
  } else if (root.val >= p.val && root.val >= q.val) {
    return lowestCommonAncestor(root.left, p, q);
  }
};
  • 二叉搜索树的后序遍历

//思路
后续遍历我们可以知道,最右边的是根节点r。
2.通过根节点r我们可以判断左子树和右子树。
3.判断左子树中的每个值是否小于r,右子树的每个值是否大于r.
4.对左、右子树递归判断。

function VerifySquenceOfBST(sequence)
{
    // write code here
    if(sequence.length<=0) return;
    return test(sequence,0,sequence.length-1)
}
function test(sequence,start,end){
    if(start>=end) return true;
    var i=end-1;
    while(i>=start && sequence[i]>sequence[end]){
        i--;
    }
    for(var j=i;j>=start;j--){
        if(sequence[j]>sequence[end]){
            return false;
        }
    }
    return test(sequence,start,i)&&test(sequence,i+1,end-1)
}
  • 二叉搜索树的第k个结点

//思路:二叉搜索树,若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;任意节点的左、右子树也分别为二叉查找树;
所以采用中序遍历的方法,遍历后的结果就是从小到大顺序的结果 function KthNode(pRoot, k) { // write code here var arr=[]; if(pRoot===null||k<1){ return null; } function midInorder(root){ if(root.left!==null){ midInorder(root.left); } arr.push(root); if(root.right!==null){ midInorder(root.right); } } midInorder(pRoot); return arr[k-1]; }
  • 将有序数组转换为二叉搜索树

var sortedArrayToBST = function(nums) {
  if (!nums.length) return null;
  let mid = Math.floor(nums.length / 2);
  let root = {
    val: nums[mid],
    left: sortedArrayToBST(nums.slice(0, mid)),
    right: sortedArrayToBST(nums.slice(mid + 1)),
  }
  return root;
}; 
  • 深度优先遍历

该方法是以纵向的维度对dom树进行遍历,从一个dom节点开始,一直遍历其子节点,直到它的所有子节点都被遍历完毕之后在遍历它的兄弟节点。
//
递归 function deepFirstSearch(node,nodeList) { if (node) { nodeList.push(node); var children = node.children; for (var i = 0; i < children.length; i++) //每次递归的时候将 需要遍历的节点 和 节点所存储的数组传下去 deepFirstSearch(children[i],nodeList); } return nodeList; }

//deepFirstSearch接受两个参数,第一个参数是需要遍历的节点,第二个是节点所存储的数组,并且返回遍历完之后的数组,该数组的元素顺序就是遍历顺序,调用方法:

  let root = document.getElementById('root')
  deepTraversal(root,nodeList=[])

//非递归
function deepFirstSearch(node) {
    var nodes = [];
    if (node != null) {
        var stack = [];
        stack.push(node);
        while (stack.length != 0) {
        var item = stack.pop();
        nodes.push(item);
        var children = item.children;
        for (var i = children.length - 1; i >= 0; i--)
            stack.push(children[i]);
        }
    }
    return nodes;
}
  • 广度优先遍历

该方法是以横向的维度对dom树进行遍历,从该节点的第一个子节点开始,遍历其所有的兄弟节点,
再遍历第一个节点的子节点,完成该遍历之后,暂时不深入,开始遍历其兄弟节点的子节点。
//非递归版本 function breadthFirstSearch(node) { var nodes = []; if (node != null) { var queue = []; queue.unshift(node); while (queue.length != 0) { var item = queue.shift(); nodes.push(item); var children = item.children; for (var i = 0; i < children.length; i++) queue.push(children[i]); } } return nodes; }

猜你喜欢

转载自www.cnblogs.com/huahongcui/p/11520996.html