“二叉树” 面试套路模板

本文总结了面试中二叉树问题的常见套路,包括递归和非递归遍历方法,如前序、中序、后序以及层次遍历。详细解释了二叉搜索树的性质和查找后继节点、前驱节点的方法。此外,还提到了动态规划在解决树结构问题中的应用,例如在防止子树重复计算时如何从底向上计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5年刷题面试经历,遇到考题千千万,面试很难恰好遇到做过的题。但解题方法是有限的,将套路梳理清楚,面试时就能快速找到相关模板套入,顺利bug-free。

今天小编梳理树题型考点套路。点赞收藏,面试前看一遍,快速通关。

遍历

1.1 递归遍历

树的考题基本都是通过递归回溯来解决,将整个问题拆分为左子树,右子树的子问题来解决,相当借助了树的遍历解决问题。

树的递归遍历分为广度优先搜索和深度优先搜索。

广度优先搜索就是层次遍历。

深度优先搜索是分为前序、中序和后序遍历,前中后序的不同在于头节点是先访问还是中间访问,还是最后访问。

前序访问顺序:头节点,左节点,右节点;中序访问顺序:左节点,头节点,右节点;后序访问顺序:右节点,头节点,左节点。

层次遍历

二叉树层次遍历需要借助队列来保存子节点。

遍历过程想区分不同层,怎么实现?

  • 使用一个queue,但是每个节点多用一个字段记录高度。能区分不同的层次。

  • 二叉树的层次遍历中可以使用两个queue,区分不同层

vector<vector<int>> levelOrder(TreeNode *root) {
   vector<vector<int>> ans;
   queue<TreeNode *>qu1;
   queue<TreeNode *>qu2;
   qu1.push(root);
   if (root==NULL)
     return ans;
   while(!qu1.empty()){
     vector <int> items;
     while (!qu1.empty()){
        TreeNode * front = qu1.front();
        items.push_back(front->val);
        if (front->left!=NULL)
          qu2.push(front->left);
        if (front->right!=NULL)
          qu2.push(front->right);
        qu1.pop();
     }
     ans.push_back(items);
     swap(qu1,qu2);
   }
   return ans;

1.2 非递归遍历

面试官也会要求使用非递归遍历实现树的遍历。

由于非递归后序遍历非常复杂,所以常考非递归实现前序,中序遍历。

这里提供一个非常好理解的实现方式。

首先,有一个功能函数putLeft,作用就是沿着root节点一路将左节点入栈。前序中序遍历的区别在于,前序遍历在putLeft过程中访问每个节点,而中序遍历是先将所有节点通过putLeft进栈,然后再依次弹出访问。

其余的代码是完全相同的,在栈弹出的过程中将右节点进行putLeft。

void putLeft(Node * root)
{
  while(root!=NULL)
  {
    st.push(root);
    root = root->left;
  }
}

非递归前序遍历

在putLeft过程中访问每个节点。

stack<Node*>st;
vector<int>nums;
void putLeft(Node * root)
{
    while(root!=NULL)
    {
        st.push(root);
        nums.push_back(root->val);
        root = root->left;
    }
}
void solute(Node * root)
{
     putLeft(root);
     while(!st.empty())
     {
        Node * topNode = st.top();
        st.pop();
        if (topNode->right!=NULL)
        {
            putLeft(topNode->right);
        }
     }
}

非递归中序遍历

先将所有节点通过putLeft进栈,然后再依次弹出访问。

stack<Node*>st;
vector<int> nums;
void putLeft(Node * root)
{
    while(root!=NULL)
    {
       st.push(root);
       root = root->left;
    }
 }
void solute(Node * root)
{
     putLeft(root);
     while(!st.empty())
     {
        Node * topNode = st.top();
        nums.push_back(topNode->val);
        st.pop();
        if (topNode->right!=NULL)
        {
            putLeft(topNode->right);
        }
     }
}

二叉搜索树

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

  • 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

  • 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

  • 左、右子树也分别为二叉排序树

二叉搜索树需要掌握以下的基础知识:

  1. 由于二叉搜索树的性质,二叉搜索树的中序遍历是升序的。

  2. Successor 代表的是中序遍历序列的下一个节点 (后继节点)。即比当前节点大的最小节点,简称后继节点。先取当前节点的右节点,然后一直取该节点的左节点,直到左节点为空,则最后指向的节点为后继节点。

     3. Predecessor 代表的是中序遍历序列的前一个节点 (前驱节点)。即比当前节点小的最大节点,简称前驱节点。先取当前节点的左节点,然后取该节点的右节点,直到右节点为空,则最后指向的节点为前驱节点。

动态规划

树结构也会出现子树重复计算,需要用动态规划来优化冗余。

刷题有术--动态规划 必备知识 中可知,动态规划思考时是从顶向下思考,思考大问题如果拆解为小问题。而在实现时是“从底向上”实现,先计算出子问题,存储下来,之后借此计算大问题。

在树结构中,借助后序遍历的方式实现“从底向上”,先将左右子树子问题计算出来,存储下来,再计算当前节点。

举个例题,打家劫舍III:

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

https://leetcode.cn/problems/house-robber-iii/

一开始遇到这个新题,考虑使用暴力搜索来解决。当A节点选择偷,则B,C节点就只能选择不偷。当A节点选择不偷,则B,C节点可以选择偷,也可以选择不偷。这里明显有重叠子问题,所以使用动态规划来优化。

使用两个map,分别记录节点不能偷 和 可以偷 两种情况下的最大收益,然后采用后序遍历的方式从底向上 将两个map 填满,最后比较root 节点两种选择的最大值即为答案。

map<TreeNode*,int> dp0;//记录节点不能偷时最大收益
map<TreeNode*,int> dp1;//记录节点可以偷时最大收益
void solute(TreeNode * root)
{
   if(root==NULL)
     return;
   solute(root->left);
   solute(root->right);
   dp1[root] = max_(root->val +dp0[root->left]+dp0[root->right], dp1[root->left]+dp1[root->right]);
   dp0[root] = dp1[root->left]+dp1[root->right];
}
int rob(TreeNode* root) {
    dp0[NULL]=0;
    dp1[NULL]=0;
    solute(root);
    return max_(dp0[root],dp1[root]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值