0 概述
这一篇文章汇总下常见的二叉树相关面试题,主要分为判断类、构建类、存储类、查找类、距离类、混合类这六类大问题。
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)。
int isBalanceBTDown2Top(BTNode *root, int *height){ if (!root) { *height = 0; return 1; } int leftHeight, rightHeight; if