(立下flag)每日10道前端面试题-22 关于【二叉树】算法题

1.对称二叉树

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

例如,二叉树 [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

进阶:

你可以运用递归和迭代两种方法解决这个问题吗?

解答:

一棵二叉树对称,则需要满足:根的左右子树是镜像对称的

也就是说,每棵树的左子树都和另外一颗树的右子树镜像对称,左子树的根节点值与右子树的根节点值相等

所以,我们需要比较:

  • 左右子树的根节点值是否相等

  • 左右子树是否镜像对称

边界条件:

  • 左右子树都为 null 时,返回 true

  • 左右子树有一个 null 时,返回 false

解法一:递归(深度优先遍历)

var isSymmetric = function(root) {
    if(!root) return true
    var isEqual = function(left, right) {
        if(!left && !right) return true
        if(!left || !right) return false
        return left.val === right.val
         && isEqual(left.left, right.right)
         && isEqual(left.right, right.left)
    }
    return isEqual(root.left, root.right)
};

复杂度分析:

  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

解法二:迭代(广度优先遍历)

利用栈来记录比较的过程,实际上,递归就使用了调用栈,所以这里我们可以使用栈来模拟递归的过程

首先根的左右子树入栈 将左右子树出栈,比较两个数是否互为镜像 如果左右子树的根节点值相等,则将左子树的 left 、右子树的 right 、左子树的 right 、右子树的 left 依次入栈 继续出栈(一次出栈两个进行比较)……. 依次循环出栈入栈,直到栈为空

var isSymmetric = function(root) {
    if(!root) return true
    let stack = [root.left, root.right]
    while(stack.length) {
        let right = stack.pop()
        let left = stack.pop()
        if(left && right) {
            if(left.val !== right.val) return false
            stack.push(left.left)
            stack.push(right.right)
            stack.push(left.right)
            stack.push(right.left)
        } else if(left || right) {
            return false
        }
    }
    return true
};

复杂度分析:

  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

2.将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

深度优先遍历:

递归构建每个子树。将数组从最中间项分割得到三个部分:子数组1,中间项,子数组2。将中间项作为当前节点的val,对子数组1和子数组2分别递归执行原方法,得到的两个子树分别作为上一个节点的左子树与右子树

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {number[]} nums
 * @return {TreeNode}
 */
var sortedArrayToBST = function(nums) {
  if (!nums.length) return null;
  const root = new TreeNode(null);

  if(nums.length > 1) root.left = sortedArrayToBST(nums.splice(0, nums.length / 2));
  root.val = nums[0];
  root.right = sortedArrayToBST(nums.splice(1));
  return root;
};

3.将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

广度优先遍历:

var levelOrderBottom = function(root) {
  if(!root)return [];
  let queue=[root];
  let res=[];
  let level=0;
  while(queue.length){
    let temp=[];
    res[level]=[];
    for(let i=0;i<queue.length;i++){
      res[level].push(queue[i].val);
      if(queue[i].left){
        temp.push(queue[i].left);
      }
      if(queue[i].right){
        temp.push(queue[i].right);
      }
    }
    queue=temp;
    ++level;
  }
  return res.reverse();
};

4.合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入: 
 Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
      3
     / \
    4   5
   / \   \ 
  5   4   7

思路:递归;

  • 一:生成一个新的tree;

  • 二:合并到的t1/t2上

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} t1
 * @param {TreeNode} t2
 * @return {TreeNode}
 */
var mergeTrees = function(t1, t2) {
    if(t1 === null){
        return t2
    }else if(t2 === null){
        return t1
    }else{
        t1.val = t1.val + t2.val
        t1.left = mergeTrees(t1.left, t2.left)
        t1.right = mergeTrees(t1.right, t2.right)

        return t1
    }
    
};

5.二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

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


示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 。

广度优先搜索

通过创建FIFO队列,迭代每一层元素,每迭代一层,level+1

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root, level = 0) {
    if (root === null) return 0
    let queue = [root]

    while(queue.length > 0) {
        let len = queue.length

        while(len--) { //把当前level的queue队列清空,同时把节点左右子节点push
            let first = queue.shift() // 左出
            if( first.left) queue.push(first.left) // 左子节点入队列
            if( first.right) queue.push(first.right) //右子节点入队列
        }
        level++
    }
    return level
};

深度优先搜索

思路

  • 找出终止条件:当前节点为空

  • 找出返回值:节点为空时说明高度为0,所以返回0;节点不为空时则分别求左右子树的高度的最大值,同时加1表示当前节点的高度,返回该数值

  • 某层的执行过程:在返回值部分基本已经描述清楚

  • 时间复杂度:O(n)

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if(!root) {
        return 0;
    } else {
        const left = maxDepth(root.left);
        const right = maxDepth(root.right);
        return Math.max(left, right) + 1;
    }
};

6.深度优先搜索

  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

思路递归交换当前节点的左右节点,当节点为null时返回

/**
 * 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 === null) {
        return null;
    }
    let right = invertTree(root.right);
    let left = invertTree(root.left);
    root.left = right;
    root.right = left;
    return 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) {
    let queue = [root];
    while(queue.length > 0){
        let cur = queue.pop();
        if(cur === null) continue;
        [cur.left,cur.right] = [cur.right,cur.left];
        queue.unshift(cur.left);
        queue.unshift(cur.right);
    }
    return root;
};

7.二叉树展开为链表

给定一个二叉树,原地将它展开为一个单链表。

例如,给定二叉树

    1
   / \
  2   5
 / \   \
3   4   6
将其展开为:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

深度优先:思路

    1
   / \
  2   5
 / \   \
3   4   6

//将 1 的左子树插入到右子树的地方
    1
     \
      2         5
     / \         \
    3   4         6        
//将原来的右子树接到左子树的最右边节点
    1
     \
      2          
     / \          
    3   4  
         \
          5
           \
            6
            
 //将 2 的左子树插入到右子树的地方
    1
     \
      2          
       \          
        3       4  
                 \
                  5
                   \
                    6   
        
 //将原来的右子树接到左子树的最右边节点
    1
     \
      2          
       \          
        3      
         \
          4  
           \
            5
             \
              6         
  


var flatten = function(root) {
    helper(root);
};

function helper(root){
    if(root==null){
        return null;
    }
    var temp;
    //如果左节点不为空,说明需要移位操作
    if(root.left!=null){
        temp=root.right;
        //把左节点挪到右节点
        root.right=root.left;
        root.left=null;
        //找到最右下得节点
        var right=findRight(root.right);
        把之前拆解下的右节点接到最右下节点
        right.right=temp;
    }
    //递归下一个右节点,知道最后
    return helper(root.right);
}
//找到一个数的最右下节点
function findRight(root){
   if(root.right==null){
       return root;
   }
   return findRight(root.right);
}

8.不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

动态规划:

每一个树的构成都可以分为左子树和右子树,所以以i为根节点的二叉搜索树种类的个数可以分解为子问题:左子树的种类 乘 右子树的种类。

左子树的节点范围为:1...i-1。右子树的节点范围为:i+1...n。

那么就可以把同样的问题转接到左子树和有字数上。

时间复杂度:O(n²)

空间复杂度:O(n)

/**
 * @param {number} n
 * @return {number}
 */
var numTrees = function(n) {
    // res[i]表示以1...i为节点组成的二叉搜索树的种类
    let res = new Array(n+1).fill(0);
    res[0] = 1;
    res[1] = 1;
    
    // res[1]已经确定,因此从2开始
    for (let i = 2; i <= n; i++) {
        // j表示分别从1为根节点至i为根节点
        for (let j = 1; j <= i; j++) {
            res[i] += res[j-1] * res[i-j];
        }
    }
    return res[n];
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值