Leetcode打卡——面试最常考的25道二叉树题你都会了吗

本文详细解析了25道常见的二叉树面试题,涵盖最大深度、对称性检查、路径总和、从遍历序列构造二叉树、填充节点指针、最近公共祖先、右视图、平衡二叉树、二叉树镜像、染色、搜索树节点、子结构、剪枝、展平、迭代器、最底层最左边值、节点之和、中序后继、最小高度树和序列化等。通过递归与迭代两种方法,结合自底向上和自顶向下策略,提供了多种解决方案。
摘要由CSDN通过智能技术生成

哈喽大家,这里是小饼干,面向就业编程,持续更新学习路径,欢迎关注点赞鸭~
在这里插入图片描述

这回整理了面试最常见的25道二叉树题,但在写这些题之前要会写最基本的二叉树遍历模板(递归和非递归),你会发现,这些题目都是在最基本的遍历基础上做了改进。
遍历不太会的小伙伴可以先看这个
点这里

不过我们通常采用递归的方法解决树的问题,这样写相较于非递归比较简洁。

自底向上——对应后序遍历 左右中 将上层值传给下层
自底向下——对应先序遍历 中左右 上层值依赖下层
差别在于是从子节点寻找答案还是从根节点寻找答案,哪个好找找哪个

1.Leetcode104.二叉树的最大深度

在这里插入图片描述
法一:自底向上

class Solution {
public:
    int maxDepth(TreeNode* root) {
          if(root==NULL)return 0;
          int left=maxDepth(root->left);
          int right=maxDepth(root->right);
          return max(left,right)+1;
    }
};

法二:自顶向下


class Solution {
public:
    int ans=0;
    int maxDepth(TreeNode* root) {
         if(root==NULL)return 0;
         search(root,0);
         return ans;
    }
    void search(TreeNode *root,int depth){
          if(root==NULL){
              ans=max(ans,depth);
              return;
          }
          search(root->left,depth+1);
          search(root->right,depth+1);
    }
};

2.Leetcode101. 对称二叉树

在这里插入图片描述

法一:递归
给定两个指针p,q,开始时都指向根结点,p左移时q右移,p右移时q左移
妙啊这解法

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        return check(root,root);
    }
    bool check(TreeNode* p,TreeNode*q){
         if(!p&&!q)return true;
         if(!p||!q)return false;
         return p->val==q->val&&check(p->right,q->left)&&check(p->left,q->right);
    }
};

法二:迭代

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root==NULL)return true;
        queue<TreeNode*>q;
        q.push(root);q.push(root);
        while(!q.empty()){
           auto l=q.front();q.pop();
           auto r=q.front();q.pop();
           if(!l&&!r)continue;
           if((!l||!r)||(l->val!=r->val))return false;
           q.push(l->left);
           q.push(r->right);
           q.push(l->right);
           q.push(r->left);

        }
        return true;
    }
   
};

3.Leetcode112. 路径总和

在这里插入图片描述
这里注意必须是根结点到叶子节点
叶子节点为左右孩子结点都为空的节点。
当根结点=目标值,且根结点一孩子节点为空另一孩子结点不为空时不满足条件,因为必须是根结点到叶子节点。根结点=叶子节点时满足条件
且根结点到一孩子节点为空另一孩子结点不为空的情况也必须排除。

法一:
自己写的,写了挺久,各种报错
感觉摸到点门道,把所有情况列出来就差不多了


class Solution {
public:
    bool flag=false;
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==NULL)return false;
        if(root)check(root,targetSum);
        return flag;
    }
    void check(TreeNode *root,int sum){
        
        if(!root->left&&!root->right){
            if(sum-root->val==0)flag=true;
            return;
        }
        //一定要对是否为空进行判断,否则直接空指针异常
        if(root->left)check(root->left,sum-root->val);
        if(root->right)check(root->right,sum-root->val);
       
    }
};

法二:
官方递归,看着比我的规整点,内容差不多

class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if (root == nullptr) {
            return false;
        }
        if (root->left == nullptr && root->right == nullptr) {
            return sum == root->val;
        }
        return hasPathSum(root->left, sum - root->val) ||
               hasPathSum(root->right, sum - root->val);
    }
};


4.Leetcode106. 从中序与后序遍历序列构造二叉树

在这里插入图片描述


class Solution {
public:
    unordered_map<int,int>hash;
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int l1=0,r1=inorder.size()-1;
        int l2=0,r2=postorder.size()-1;
        
        /*哈希表用来快速查找中序遍历中跟节点的位置*/
        for(int i=0;i<=r1;i++){
            hash[inorder[i]]=i;
        }
        return build(l1,r1,l2,r2,inorder,postorder);

    }
    TreeNode *build(int l1,int r1,int l2,int r2,vector<int> inorder, vector<int> postorder){
        if(l1>r1)return NULL;
        int midvalue=postorder[r2];
        /*len用来记录左边序列长度*/
        /*易写成根据坐标容易错误划分划分前序遍历中左子树*/
        int len=hash[midvalue]-l1;
        TreeNode *root=new TreeNode(midvalue);
        TreeNode *left=build(l1,l1+len-1,l2,l2+len-1,inorder,postorder);
        TreeNode *right=build(l1+len+1,r1,l2+len,r2-1,inorder,postorder);
        
        root->left=left;
        root->right=right;
        return root;
    }
};

参考了官解进行优化

class Solution {
public:
    unordered_map<int,int>hash;
    int post_idx;
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int l=0,r=inorder.size()-1;
        
        /*哈希表用来快速查找中序遍历中跟节点的位置*/
        for(int i=0;i<=r1;i++){
            hash[inorder[i]]=i;
        }
        post_idx=postorder.size()-1;
        return build(l,r,inorder,postorder);

    }
    TreeNode *build(int l,int r,vector<int> inorder, vector<int> postorder){
        if(l>r)return NULL;
        int midvalue=postorder[post_idx];
        /*len用来记录左边序列长度*/
        /*易写成根据坐标容易错误划分划分前序遍历中左子树*/
        int len=hash[midvalue]-l;
        TreeNode *root=new TreeNode(midvalue); 
        post_idx--;
        /*官解这里很妙,右子树先进行递归,其后序遍历序列末尾节点依次减一恰好对应其中间节点*/
        TreeNode *right=build(l+len+1,r,inorder,postorder);
        TreeNode *left=build(l,l+len-1,inorder,postorder);
        root->left=left;
        root->right=right;
      
        return root;
    }
};

5.Leetcode105. 从前序与中序遍历序列构造二叉树

在这里插入图片描述

class Solution {
public:
    unordered_map<int,int>hash;
    int pre_index;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
         int len=inorder.size();
         for(int i=0;i<len;i++){
             hash[inorder[i]]=i;
         }
         pre_index=0;
         TreeNode *root=build(0,len-1,preorder,inorder);
         return root;
    }
    TreeNode* build(int l,int r,vector<int>& preorder, vector<int>& inorder){
         if(l>r)return NULL;
         int midvalue=preorder[pre_index];
         int len=hash[midvalue]-l;
         TreeNode *root=new TreeNode(midvalue);
         pre_index++;
         TreeNode *left=build(l,l+len-1,preorder,inorder);
         TreeNode *right=build(l+len+1,r,preorder,inorder);
         root->left=left;
         root->right=right;
         return root;
    }
};

6.Leetcode116. 填充每个节点的下一个右侧节点指针

在这里插入图片描述
在这里插入图片描述
给的是完美二叉树,直接广搜就行


class Solution {
public:
    Node* connect(Node* root) {
        if(root==NULL)return NULL;
        queue<Node*>q;
        q.push(root);
        while(!q.empty()){
            int len=q.size();
            for(int i=0;i<len;i++){
                auto t=q.front();
                q.pop();
                if(i!=len-1)t->next=q.front();
                if(t->left)q.push(t->left);
                if(t->right)q.push(t->right);
            }
            
        }
        return root;
    }
};

7.Leetcode117. 填充每个节点的下一个右侧节点指针 II

在这里插入图片描述
这次给的没说是完美二叉树了,但是不影响解题
原来6的位置为空时,5直接接7,上一题的代码也能过
在这里插入图片描述
优化解法:
队列的入队出队会带来一定的时间消耗
将每一层串成链表

class Solution {
public:
    Node* connect(Node* root) {
        if(root==NULL)return NULL;
        Node *chead=new Node(0),*pcur,*ccur;
        pcur=root;
        ccur=chead;
        while(true){
            while(pcur){
                if(pcur->left){
                    ccur->next=pcur->left;
                    ccur=ccur->next;
                }
                if(pcur->right){
                    ccur->next=pcur->right;
                    ccur=ccur->next;
                }      
                pcur=pcur->next;
            }
            if(chead->next==NULL)break;

            pcur=chead->next;
            ccur=chead;
            chead->next=NULL;
        }

        return root;
    }
};

8.Leetcode236. 二叉树的最近公共祖先

在这里插入图片描述
求最近公共祖先,即自底向上求左右子树各包含p,q之一的根节点,或者一根结点为p/q,子树包含q/p一节点的根节点
自底向上对应后序遍历

class Solution {
public:
    TreeNode *ans;
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
       search(root,p,q);
       return ans;   
    }
    bool search(TreeNode* root, TreeNode* p, TreeNode* q){
        if(root==NULL)return false;
        bool getl=search(root->left,p,q);
        bool getr=search(root->right,p,q);
        if(getl&&getr||(getl||getr)&&(root==p||root==q)){
            ans=root;
            return true;
        }
        return root==p||root==q||getl||getr;
    }
    
};

9.199. 二叉树的右视图

在这里插入图片描述
法一:直接bfs,记录每层最后一个节点

class Solution {
public:
    vector<int>ans;
    vector<int> rightSideView(TreeNode* root) {
         if(!root)return ans;
         queue<TreeNode*>q;
         q.push(root);
         while(!q.empty()){
             int len=q.size();
             for(int i=0;i<len;i++){
                 auto t=q.front();
                 if(i==len-1)ans.push_back(t->val);
                 q.pop(); 
                 if(t->left)q.push(t->left);
                 if(t->right)q.push(t->right); 
             }   
        
         }
         return ans;
    }
};

法二:按照一个中右左的遍历顺序进行遍历

class Solution {
public:
    vector<int>ans;
    int depth=0;
    vector<int> rightSideView(TreeNode* root) {
        search(root,0);
        return ans;
    }
    void  search(TreeNode *root,int depth){
        if(root==NULL)return;
        /*当ans.size()==depth,说明此节点时当前深度下被第一个访问到的节点,这里很妙*/
        if(ans.size()==depth)ans.push_back(root->val);
        depth++;
        search(root->right,depth);
        search(root->left,depth);

    }


};

10.剑指 Offer 55 - II. 平衡二叉树

class Solution {
public:
    bool ans=true;
    bool isBalanced(TreeNode* root) {
        dfs(root);
        return ans;
    }
    int dfs(TreeNode *root){
        if(root==NULL)return 0;
        int left=dfs(root->left);
        int right=dfs(root->right);
        if(abs(left-right)>1){
            ans=false;
            return -1;
        }
        return max(left,right)+1;
    }
};

11.剑指 Offer 27. 二叉树的镜像

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(!root)return NULL;
        TreeNode *newroot=new TreeNode(root->val);
        copy(root,newroot);
        return newroot;
    }
    void copy(TreeNode *root,TreeNode *newroot){
        if(root==NULL)return;
        if(root->right){
            newroot->left=new TreeNode(root->right->val);
            copy(root->right,newroot->left);
        }
        if(root->left){
           newroot->right=new TreeNode(root->left->val); 
           copy(root->left,newroot->right);
        }
        return;

    }
};

12.LCP 34. 二叉树染色

在这里插入图片描述

自己研究了一阵,发现只会递归算一条路径上的,整棵树的连续相邻子节点个数不超过k的个数就想不到怎么写了
看了下大佬的题解恍然大悟,自底向上,最重要的还是对各种情况的判断,
下一种情况又依赖于上一种情况,就可以用dp写

参考for_ak大佬的题解:
在这里插入图片描述
整个是一个后序遍历,自底向上

class Solution {
    vector<int> dfs(TreeNode* root, int k) {
        vector<int> f(k + 1, 0);
        if (!root) return f;
        
        auto l = dfs(root->left, k);
        auto r = dfs(root->right, k);
        /*当前节点不染色最大值=左子树任意情况+右子树任意情况下的最大值*/
        f[0] = *max_element(l.begin(), l.end()) + *max_element(r.begin(), r.end());
        for (int i = 1; i <= k; ++i) {
            for (int j = 0; j < i; ++j) {
                /*在当前节点不染色,和当前节点染色(左子树染色j个,右子树染色i-j-1个)*/
                f[i] = max(f[i], root->val + l[j] + r[i - j - 1]);
            }
        }
        return f;
    }
public:
    int maxValue(TreeNode* root, int k) {
        auto f = dfs(root, k);
        return *max_element(f.begin(), f.end());
    }
};


13.剑指 Offer 36. 二叉搜索树与双向链表

在这里插入图片描述
法一:直接套了迭代的模板,成功AC

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if(!root)return root;
        Node *head=new Node(0),*pre=NULL,*cur=head;
        stack<Node*>st;
        Node *node=root;
        while(node||!st.empty()){ 
            while(node){
                st.push(node);
                node=node->left;
            }
        
            node=st.top();
            st.pop();

            cur->right=node;
            cur->left=pre;
            pre=cur;
            cur=cur->right;
            

            node=node->right;
        }

        cur->right=head->right;
        cur->left=pre;
        head->right->left=cur;
        
        return head->right;

    }
};

法二:递归
改一改中序遍历的递归就行

class Solution {
public:
    Node *head=new Node(0),*cur=head,*pre=NULL;
    Node* treeToDoublyList(Node* root) {
        if(root==NULL)return root;
       dfs(root);
       cur->right=head->right;
       cur->left=pre;
       head->right->left=cur;
       return head->right;
    }
    Node *dfs(Node *root){
        if(root==NULL)return root; 
        dfs(root->left);
        cur->right=root;
        cur->left=pre; 
        pre=cur;
        cur=cur->right;
        dfs(root->right);
        return root;
    }
};

14.剑指 Offer 32 - III. 从上到下打印二叉树 III

在这里插入图片描述

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>ans;
        if(root==NULL)return ans;
         queue<TreeNode*>q;
         q.push(root);
         bool flag=false;
         while(!q.empty()){
             vector<int>line;
             int len=q.size();

             for(int i=0;i<len;i++){
                auto t=q.front();
                q.pop();
                line.push_back(t->val);
                if(t->left)q.push(t->left);
                if(t->right)q.push(t->right); 
             }

             if(flag){
                 reverse(line.begin(),line.end());
             }
             flag=!flag;
             ans.push_back(line);           
             
         }
         return ans;
    }
};

15.剑指 Offer 54. 二叉搜索树的第k大节点

在这里插入图片描述

class Solution {
public:
    int num=0,ans;
    int kthLargest(TreeNode* root, int k) {
        search(root,k);
        return ans;
    }
    void search(TreeNode*root,int k ){
        if(!root)return;
        search(root->right,k);
        
        num++;
        if(num==k){
            ans=root->val;
            return;
        }
        search(root->left,k);
       
    }
};

16.剑指 Offer 33. 二叉搜索树的后序遍历序列

在这里插入图片描述
一个后序遍历序列可以划分为左子树的后序遍历序列(小于根结点)、右子树的后序遍历序列(大于根节点)、根结点

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.size()==0)return true;
       bool ans=search(postorder,0,postorder.size()-1);
       return ans;
    }
    bool search(vector<int>postorder ,int l,int r){
        if(l>=r)return true;
        int m=l;
        while(postorder[m]<postorder[r])m++;
        int t=m;
        while(postorder[t]>postorder[r])t++;
        return t==r&&search(postorder,l,m-1)&&search(postorder,m,r-1);
    }
};

17.剑指 Offer 26. 树的子结构

在这里插入图片描述

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
         if(A==NULL||B==NULL)return false;
         bool ans=search(A,B);
         return ans||isSubStructure(A->left,B)||isSubStructure(A->right,B);
    }
    bool search(TreeNode *A,TreeNode *B){
       if(!B)return true;
       if(!A)return false;
       return (A->val==B->val)&&search(A->left,B->left)&&search(A->right,B->right);
    }
};

18.剑指 Offer II 047. 二叉树剪枝

在这里插入图片描述

class Solution {
public:
    TreeNode* pruneTree(TreeNode* root) {
        if(root==NULL)return NULL;
         bool ans=search(root);
         if(ans)return NULL;
         return root;
    }
    bool search(TreeNode* root){
        if(root==NULL)return true;
        bool l=search(root->left);
        bool r=search(root->right); 
        if(l)root->left=NULL;
        if(r)root->right=NULL;

        return root->val==0&&l&&r;
    }
};

19.剑指 Offer II 052. 展平二叉搜索树

在这里插入图片描述

class Solution {
public:
    TreeNode *head=new TreeNode(0),*cur=head;
    TreeNode* increasingBST(TreeNode* root) {
        if(root==NULL)return root;
        dfs(root);
        cur->right=NULL;
        cur->left=NULL;
        return head->right;
    }
    TreeNode *dfs(TreeNode* root){
        if(root==NULL)return NULL;
        dfs(root->left);
        cur->right=root;
        root->left=NULL;
        cur=cur->right;
        dfs(root->right);
        
        return root;
    }
};

20.剑指 Offer II 055. 二叉搜索树迭代器

在这里插入图片描述
法一:扁平化:中序遍历一遍,存到数组里,通过数组实现迭代器

class BSTIterator {
public:
    vector<int>res;
    int i;
    void Inorder(TreeNode* root){
        if(root==NULL)return;
        Inorder(root->left);
        res.push_back(root->val);
        Inorder(root->right);
    }
    BSTIterator(TreeNode* root) {
        Inorder(root);
        i=0;
    }
    
    int next() {
        return res[i++];
    }
    
    bool hasNext() {
       if(i<res.size())return true;
       return false;
    }
};


法二:利用栈这一数据结构,通过迭代的方式对二叉树做中序遍历。无需预先计算出中序遍历的全部结果,只需要实时维护当前栈的情况即可。

class BSTIterator {
public:
   
    stack<TreeNode *>st;
    TreeNode *cur;
    BSTIterator(TreeNode* root) {
        cur=root;
    }
    
    int next() {
        while(cur){
            st.push(cur);
            cur=cur->left;
        }
        cur=st.top();
        st.pop();
        int res=cur->val;
        cur=cur->right;
        return res;
    }
    
    bool hasNext() {
       return (!st.empty()||cur);
    }
};

21.剑指 Offer II 045. 二叉树最底层最左边的值

在这里插入图片描述

class Solution {
public:
    int res=0,max=-1;
    int findBottomLeftValue(TreeNode* root) {
        find(root,0);
        return res;
    }
    void find(TreeNode *root,int depth){
        if(root==NULL)return;
        find(root->left,depth+1);
        /*上一句总能找到最左,在最左里找深度最大进行更新*/
        if(depth>max){
            max=depth;
            res=root->val;
        }
        find(root->right,depth+1);
        
    }
    
};

22.剑指 Offer II 056. 二叉搜索树中两个节点之和

在这里插入图片描述
法一:利用哈希表存值进行判断


class Solution {
public:
    unordered_map<int,bool>hash;
    bool findTarget(TreeNode* root, int k) {
       if(root==NULL)return false;
       stack<TreeNode*>st;
       st.push(root);
       TreeNode *cur=root;
       while(!st.empty()||cur){
           while(cur){
               st.push(cur);
               cur=cur->left;
           }
           cur=st.top();
           st.pop();
           hash[cur->val]=true;
           if(hash[k-cur->val]&&k!=2*cur->val){
               return true;
           }
           cur=cur->right;
       }
       return false;

    }
    
};

法二:双指针法,利用二叉树平衡树的特点,左子树上的值都小于根结点,右子树上的值都大于根结点,设立两个指针left和right分别指向左子树和右子树上的节点
如果当前 left + right < k ,则 left 指向下一个节点值:left = 中序遍历迭代的下一个节点值;
如果 left + right > k,则 right 指向下一个节点值:right = 反中序遍历迭代的下一个节点值

23.剑指 Offer II 053. 二叉搜索树中的中序后继

法一:常规中序遍历找
法二:按照二叉搜索树的性质

class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        if(root==NULL)return NULL;
        TreeNode* res=NULL;
        TreeNode *cur=root;
        while(cur){
            if(cur->val>p->val){
                res=cur;
                cur=cur->left;
            }else{
                cur=cur->right;
            }
        }
        return res;
    }
    
};

24.面试题 04.02. 最小高度树

在这里插入图片描述

class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        if(nums.empty())return NULL;
        TreeNode *root=build(nums,0,nums.size());
        return root;
    }
    TreeNode *build(vector<int>&nums,int l,int r){
        if(l>=r)return NULL;
        int mid=(l+r)/2;
        TreeNode *root=new TreeNode(nums[mid]);
        TreeNode *left=build(nums,l,mid);
        TreeNode *right=build(nums,mid+1,r);
        root->right=right;
        root->left=left;
        return root;
    }
};

25.Leetcode297. 二叉树的序列化与反序列化||剑指 Offer 37. 序列化二叉树

在这里插入图片描述

class Codec {
public:

    string str;
    string serialize(TreeNode* root) {
       
        if(root==NULL)return str;
        Inorder(root);
        return str;
    }
    void Inorder(TreeNode *root){
         if(root==NULL){
             str+="N,";
             return;
         }
         /*前序遍历*/
         str+=to_string(root->val)+",";
         Inorder(root->left);
         
         Inorder(root->right);
    }

    int i=0;
    TreeNode* deserialize(string data) { 
       cout<<data;
        if(data.length()==0)return NULL;
      
       TreeNode *root=ReInorser(data);
       return root; 
    }
    TreeNode* ReInorser(string data){
        if(data[i]=='N'){
            i+=2;
            return NULL;
        }
        TreeNode *root;
        int num=0;
        bool flag=false;
        if(data[i]=='-'){
            i++;
            flag=true;
        }
        while(data[i]!=','){
            num=num*10+data[i++]-'0';
        }
        i++;
        root=new TreeNode(flag?-num:num);
       
        
        TreeNode *left=ReInorser(data);
        TreeNode *right=ReInorser(data);
        root->left=left;
        root->right=right;
        return root;

    }
};


评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值