47面试常问:六大类二叉树面试题汇总解答

 

0 概述

继上一篇总结了二叉树的基础操作后,这一篇文章汇总下常见的二叉树相关面试题,主要分为判断类、构建类、存储类、查找类、距离类、混合类这六类大问题。

本文所有代码

https://github.com/shishujuan/dsalg/tree/master/code/ds/tree/binary_tree

1 判断类问题

判断类问题主要分下下判断二叉树是否是二叉搜索树、二叉完全树,以及两棵二叉树是否同构这三个问题。

1.1 判断一棵二叉树是否是二叉搜索树(BST)

题:给定一棵二叉树,判断该二叉树是否是二叉搜索树。

二叉搜索树是一种二叉树,但是它有附加的一些约束条件,这些约束条件必须对每个结点都成立:

  • 结点的左子树所有结点的值都小于等于该结点的值。

  • 结点的右子树所有结点的值都大于该结点的值。

  • 结点的左右子树同样都必须是二叉搜索树。

一种错误解法

初看这个问题,容易这么实现:假定当前结点值为 k,对于二叉树中每个结点,判断其左孩子的值是否小于 k,其右孩子的值是否大于 k。如果所有结点都满足该条件,则该二叉树是一棵二叉搜索树。

实现代码如下:

int isBSTError(BTNode *root)
{
    if (!root) return 1;  

    if (root->left && root->left->value >= root->value)  
        return 0;  

    if (root->right && root->right->value < root->value)  
        return 0;  

    if (!isBSTError(root->left) || !isBSTError(root->right))  
        return 0;  

    return 1;  
}

很不幸,这种做法是错误的,如下面这棵二叉树满足上面的条件,但是它并不是二叉搜索树。

    10
   /  \
  5    15     -------- binary tree(1) 符合上述条件的二叉树,但是并不是二叉搜索树。
      /  \
     6    20

解1:蛮力法

上面的错误解法是因为判断不完整导致,可以这样来判断:

  • 判断结点左子树最大值是否大于等于结点的值,如果是,则该二叉树不是二叉搜索树,否则继续下一步判断…

  • 判断右子树最小值是否小于或等于结点的值,如果是,则不是二叉搜索树,否则继续下一步判断。

  • 递归判断左右子树是否是二叉搜索树。(代码中的 bstMax 和 bstMin 函数功能分别是返回二叉树中的最大值和最小值结点,这里假定二叉树为二叉搜索树,实际返回的不一定是最大值和最小值结点)

     

int isBSTUnefficient(BTNode *root)
{
    if (!root) return 1;

    if (root->left && bstMax(root->left)->value >= root->value)
        return 0;

    if (root->right && bstMin(root->right)->value < root->value)
        return 0;

    if (!isBSTUnefficient(root->left) || !isBSTUnefficient(root->right))
        return 0;

    return 1;
}

解2:一次遍历法

以前面提到的 binary tree(1) 为例,当我们遍历到结点 15 时,我们知道右子树结点值肯定都 >=10。当我们遍历到结点 15 的左孩子结点 6 时,我们知道结点 15 的左子树结点值都必须在 10 到 15 之间。显然,结点 6 不符合条件,因此它不是一棵二叉搜索树。

int isBSTEfficient(BTNode* root, BTNode *left, BTNode *right)
{
   if (!root) return 1;

   if (left && root->value <= left->value)
       return 0;

   if (right && root->value > right->value)
       return 0;

   return isBSTEfficient(root->left, left, root) && isBSTEfficient(root->right, root, right);
}

解3:中序遍历解法

还可以模拟树的中序遍历来判断BST,可以直接将中序遍历的结果存到一个辅助数组,然后判断数组是否有序即可判断是否是BST。当然,我们可以不用辅助数组,在遍历时通过保留前一个指针 prev,据此来实现判断BST的解法,初始时 prev = NULL。

int isBSTInOrder(BTNode *root, BTNode *prev)
{
    if (!root) return 1;

    if (!isBSTInOrder(root->left, prev))
        return 0;

    if (prev && root->value < prev->value)
        return 0;

    return isBSTInOrder(root->right, root);
}

1.2 判断二叉树是否是完全二叉树

题:给定一棵二叉树,判断该二叉树是否是完全二叉树(完全二叉树定义:若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树)。

解1:常规解法-中序遍历

先定义一个 满结点 的概念:即一个结点存在左右孩子结点,则该结点为满结点。在代码中定义变量 flag 来标识是否发现非满结点,为1表示该二叉树存在非满结点。完全二叉树如果存在非满结点,则根据层序遍历队列中剩下结点必须是叶子结点,且如果一个结点的左孩子为空,则右孩子结点也必须为空。

int isCompleteBTLevelOrder(BTNode *root)
{
    if (!root) return 1;

    BTNodeQueue *queue = queueNew(btSize(root));
    enqueue(queue, root);

    int flag = 0;
    while (QUEUE_SIZE(queue) > 0) {
        BTNode *node = dequeue(queue);
        if (node->left) {
            if (flag) return 0;
            enqueue(queue, node->left);
        } else {
            flag = 1;
        }

        if (node->right) {
            if (flag) return 0;
            enqueue(queue, node->right);
        } else {
            flag = 1;
        }
    }
    return 1;
}

解2:更简单的方法-判断结点序号法

更简单的方法是判断结点序号法,因为完全二叉树的结点序号都是有规律的,如结点 i 的左右子结点序号为 2i+1 和 2i+2,如根结点序号是 0,它的左右子结点序号是 1 和 2(如果都存在的话)。

我们可以计算二叉树的结点数目,然后依次判断所有结点的序号,如果不是完全二叉树,那肯定会存在结点它的序号大于等于结点数目的。如前面提到的 binary tree(1) 就不是完全二叉树。

    10(0)
   /  \
  5(1) 15(2)    - 结点数目为5,如果是完全二叉树结点最大的序号应该是4,而它的是6,所以不是。
      /  \
     6(5) 20(6)

实现代码如下:

int isCompleteBTIndexMethod(BTNode *root, int index, int nodeCount)
{
    if (!root) return 1;

    if (index >= nodeCount)
        return 0;

    return (isCompleteBTIndexMethod(root->left, 2*index+1, nodeCount) &&
            isCompleteBTIndexMethod(root->right, 2*index+2, nodeCount));
}

1.3 判断平衡二叉树

题:判断一棵二叉树是否是平衡二叉树。所谓平衡二叉树,指的是其任意结点的左右子树高度之差不大于1。

     __2__
    /     \
   1       4       ---- 平衡二叉树示例
    \     / \
     3   5   6

解1:自顶向下方法

判断一棵二叉树是否是平衡的,对每个结点计算左右子树高度差是否大于1即可,时间复杂度为O(N^2) 。

int isBalanceBTTop2Down(BTNode *root)
{
    if (!root) return 1;

    int leftHeight = btHeight(root->left);
    int rightHeight = btHeight(root->right);
    int hDiff = abs(leftHeight - rightHeight);

    if (hDiff > 1) return 0;

    return isBalanceBTTop2Down(root->left) && isBalanceBTTop2Down(root->right);
}

解2:自底向上方法

因为解1会重复的遍历很多结点,为此我们可以采用类似后序遍历的方式,自底向上来判断左右子树的高度差,这样时间复杂度为 O(N)。</

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值