剑指offer - 二叉树专题

概述

树的相关问题是面试考察的重难点,其中大多数题目都是利用DFS求解的。 对于DFS而言,一定要明确递归函数的含义以及函数内部的操作顺序



1. 二叉树的镜像

  • 题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。
在这里插入图片描述
原题链接

  • 思路

    观察镜像后的二叉树和原二叉树的区别,所谓“二叉树镜像”是将原二叉树的每个节点左右孩子进行交换。

    class Solution {
    public:
    
        TreeNode* Mirror(TreeNode* pRoot) { //交换root为根的左右孩子
            
            mirror(pRoot);
            return pRoot;
        }
        
        void mirror(TreeNode *root) {
            
            if(!root) return;
            mirror(root->left);
            mirror(root->right);
            swap(root->left, root->right);
        }
    };
    
    



2. 二叉树的深度

  • 题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
原题链接

  • 思路

    递归函数的含义是:求以root为根的子树的高度。

    /*
    struct TreeNode {
    	int val;
    	struct TreeNode *left;
    	struct TreeNode *right;
    	TreeNode(int x) :
    			val(x), left(NULL), right(NULL) {
    	}
    };*/
    class Solution {
    public:
        int TreeDepth(TreeNode* pRoot) {
            
            if(!pRoot) return 0;
            return max(TreeDepth(pRoot->left), TreeDepth(pRoot->right)) + 1;
        }
    };
    
    



3. 平衡二叉树

  • 题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树;
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树;
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
注:我们约定空树是平衡二叉树。
原题链接

  • 思路

    此题和上题做法类似,只不过在递归求高度时,看存不存在某节点的左右子树高度差的绝对值大于1, 用flag记录一下即可。

    class Solution {
    public:
        
        bool flag = true;
        bool IsBalanced_Solution(TreeNode* pRoot) {
            
            bfs(pRoot);
            return flag;
        }
        
        int bfs(TreeNode *root) {
            
            if(!root) return 0;
            int left = bfs(root->left), right = bfs(root->right);
            if(abs(left - right) > 1) flag = false;
            return max(left, right) + 1;
         }
    };
    
    



4. 二叉搜索树的第k个节点 🌟

  • 题目描述

给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。
原题链接

  • 思路

    BST的性质是:对于二叉树的每一个节点,都满足左子树 < 节点 < 右子树。
    ps:这里的左右子树指的是左右子树的全部节点。

    根据此性质,我们可以知道:二叉树的中序遍历序列是一个排序序列。

    /*
    struct TreeNode {
        int val;
        struct TreeNode *left;
        struct TreeNode *right;
        TreeNode(int x) :
                val(x), left(NULL), right(NULL) {
        }
    };
    */
    class Solution {
    public:
        
        TreeNode *_kth;
        TreeNode* KthNode(TreeNode* pRoot, int k) {
            
            dfs(pRoot, k);
            return _kth;
        }
        
        void dfs(TreeNode *root, int &k) {
            
            if(!root) return;
            dfs(root->left, k);
            k --;
            if(!k) _kth = root;
            if(k > 0) dfs(root->right, k);
        }
    };
    
    

    还有一种非递归的写法,不断将左孩子入栈直到空。

    /*
    struct TreeNode {
        int val;
        struct TreeNode *left;
        struct TreeNode *right;
        TreeNode(int x) :
                val(x), left(NULL), right(NULL) {
        }
    };
    */
    class Solution {
    public:
        TreeNode* KthNode(TreeNode* pRoot, int k) {
            
            if(!pRoot) return nullptr;
            stack<TreeNode *> res;
            TreeNode *p = pRoot;
            while(!res.empty() || p) {
                
                while(p) {
                    
                    res.push(p);
                    p = p->left;
                }
                TreeNode *node = res.top();
                res.pop();
                if((-- k) == 0) return node;
                p = node->right;
            }
            return nullptr;
        }
    };
    
    



5. 重建二叉树 🌟

  • 题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
原题链接

  • 思路

    我们首先分析一下前序遍历中序遍历是如何进行的;

    对于前序遍历而言,先遍历树的根节点,再遍历左子树,左子树遍历完成后就遍历右子树。这其中,对左右子树的遍历也采用前序遍历的方式。

    对于后序遍历而言,先遍历左子树,再遍历树的根节点,最后遍历右子树。同样,对左右子树的遍历采用后序遍历的方式。

    因此,对于前序序列中序序列而言,前序序列是由三个部分:根、左子树序列、右子树序列构成的,左子树序列和右子树序列同样也是由这三个部分构成。算法步骤如下:

    • 先通过前序序列获取树的根节点;
    • 然后根据树根从中序序列中获取左右子树的节点个数;
    • 再回到前序序列中,分别确定左子树和右子树的前序序列,重复 1 ~ 3;
    • 这显然是个递归的过程,终止条件是树的前序子序列为空
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    
    unordered_map<int , int> um;
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> in) {
        
        if(!pre.size()) return NULL;
        int n = pre.size();
        for(int i = 0; i < n; i ++)
           um[in[i]] = i;
        return dfs(pre, in, 0, n - 1, 0, n - 1);
    }
    
    TreeNode* dfs(vector<int> &pre, vector<int> &in, int pl, int pr, int il, int ir) {
        
        if(pl > pr) return NULL;
        TreeNode *root = new TreeNode(pre[pl]);
        int l_num = um[pre[pl]] - il;
        root->left = dfs(pre, in, pl + 1, pl + l_num, il, il + l_num - 1);
        root->right = dfs(pre, in, pl + l_num + 1, pr, il + l_num + 1, ir);
        return root;
    }
};




6. 二叉树的下一个节点 🌟

  • 题目描述

输入描述:
输入分为2段,第一段是整体的二叉树,第二段是给定二叉树节点的值,后台会将这2个参数组装为一个二叉树局部的子树传入到函数GetNext里面,用户得到的输入只有一个子树根节点。
返回值描述:
返回传入的子树根节点的下一个节点,后台会打印输出这个节点。
原题链接

  • 思路

    二叉树的中序遍历是按照左根右的顺序进行遍历的, 因此可以分如下情况进行讨论:

    • 如果当前节点有右子树,则下个节点为右子树最左侧的节点
    • 如果当前节点没有右子树,又可以分为两种情况:
      • 如果当前节点为其根节点的左孩子,则下个节点为其根节点
      • 如果当前节点为其根节点的右孩子,则下个节点为某一祖父节点,此祖父节点一定为其父节点的左孩子
/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        
        if(pNode->right) {
            
            pNode = pNode->right;
            while(pNode->left) 
                pNode = pNode->left;
            return pNode;
        }
        
        while(pNode->next && pNode->next->left != pNode) pNode = pNode->next;
        return pNode->next;
    }
};




7. 把二叉树打印成多行

  • 题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
原题链接

  • 思路

    此题考察二叉树的层序遍历,但是题目要求将每一层的数据分隔开,我们只需要在层序遍历的同时携带节点的层数即可;

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    
        vector<vector<int> > Print(TreeNode* pRoot) {
            
            int n = getDepth(pRoot);
            vector<vector<int>> res(n, vector<int>());
            if(!pRoot) return res;
            queue<pair<int, TreeNode*>> q;
            q.push({0, pRoot});
            
            while(!q.empty()) {
               
                auto t = q.front();
                q.pop();
                res[t.first].push_back(t.second->val);
                
                if(t.second->left || t.second->right) {
                    
                    int l = t.first + 1;
                    if(t.second->left) q.push({l, t.second->left});
                    if(t.second->right) q.push({l, t.second->right});
                }
            }
            return res;
        }
        
        int getDepth(TreeNode *root) {
            
            if(!root) return 0;
            return max(getDepth(root->left), getDepth(root->right)) + 1;
        }
    
};




8. 树的子结构 🌟🌟

  • 题目描述

描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
示例1
输入:
{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值:
true
原题链接

  • 思路

    给定A、B,判断B是否为A的子结构。我们可以思考一个问题,如果B为A的子结构会有哪些情况?

    • B的根和A的根相同;
    • B的根和A的除根外某一个节点相同。

    既然上面这两种情况都有可能发生,也就是说我们需要遍历A树的所有节点,如果这其中存在某一节点构成的子树能够包含B,那么B就为A的子结构。因此,我们必须遍历A树。

    上面说明了判断B是否为A的子结构需要遍历A树,但如何判断子结构呢?这实际也是一个递归的过程,下面给出具体代码:

    /*
    struct TreeNode {
    	int val;
    	struct TreeNode *left;
    	struct TreeNode *right;
    	TreeNode(int x) :
    			val(x), left(NULL), right(NULL) {
    	}
    };*/
    class Solution {
    public:
        bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
            
            
            if(!pRoot1 || !pRoot2) return false;
            
            if(isSame(pRoot1, pRoot2)) return true;
            return HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
        }
        
        bool isSame(TreeNode *root1, TreeNode *root2) {
            
            if(!root2) return true;
            if(!root1 || root1->val != root2->val) return false;
            return isSame(root1->left, root2->left) && isSame(root1->right, root2->right);
        }
    };
    
    



9. 从上往下打印二叉树

  • 题目描述

描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
示例1
输入:
{5,4,#,3,#,2,#,1}
返回值:
[5,4,3,2,1]
原题链接

  • 思路

    这题就是层序遍历,但不知道为啥牛客上设为了困难。

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        
        vector<int> res;
        if(!root) return res;
        
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()) {
            
            TreeNode *t = q.front();
            q.pop();
            res.push_back(t->val);
            if(t->left) q.push(t->left);
            if(t->right) q.push(t->right);
        }
        return res;
    }
};




10.二叉搜索树的后序遍历序列

  • 题目描述

描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜素树)
示例1
输入:
[4,8,6,12,16,14,10]
返回值:
true
原题链接

  • 思路

    思路和给定前序和中序重建二叉树类似。

    首先需要清楚BST的性质:左子树的所有点 < 根 < 右子树的所有点。

    对于二叉树的遍历序列满足某种性质的题目,通用思路就是递归划分子序列,如果所有子序列都满足该性质,那么二叉树就满足。

class Solution {
public:
    
    vector<int> seq;
    bool VerifySquenceOfBST(vector<int> sequence) {
        
        seq = sequence;
        if(seq.empty()) return false;
        return dfs(0, seq.size() - 1);
    }
    
    bool dfs(int l, int r) {
        
        if(l >= r) return true;
        
        int root = seq[r];
        int k = l;
        while(k < r && seq[k] < root) k ++;
        for(int i = k; i < r; i ++)
            if(seq[i] < root)
                return false;
        
        return dfs(l, k - 1) && dfs(k, r - 1);
    }
};




11.二叉树中和为某一值的路径 🌟

  • 题目描述

描述
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
示例1
输入:
{10,5,12,4,7},22
返回值:
[[10,5,7],[10,12]]
示例2
输入:
{10,5,12,4,7},15
返回值:
[]
原题链接

  • 思路

    这是比较典型的DFS题目,遍历所有路径将满足条件的路径记录下来,这其中一定要注意的是当递归函数返回时 (即回溯时) 要恢复现场

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    
    vector<vector<int>> res;
    vector<vector<int> > FindPath(TreeNode* root,int sum) {
        
        vector<int> path;
        dfs(root, sum, path);
        return res;
    }
    
    void dfs(TreeNode *root, int sum, vector<int> &path) {
        
        if(!root) return;
        path.push_back(root->val);
        sum -= root->val;
        if(!root->left && !root->right && !sum) 
            res.push_back(path);
        
        dfs(root->left, sum, path);
        dfs(root->right, sum, path);
        
        path.pop_back();
        sum += root->val;
    }
};




12.对称的二叉树 🌟

  • 题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
原题链接

  • 思路

判断一棵二叉树是否对称 <=> 递归判断这棵二叉树的左右子树是否互为镜像。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot) {
        
        return !pRoot || dfs(pRoot->left, pRoot->right);
    }
    
    bool dfs(TreeNode *l ,TreeNode *r) {
        
        if(!l || !r) return !l && !r;
        return l->val == r->val && dfs(l->left, r->right) && dfs(l->right, r->left);
    }

};




13.按之字形顺序打印二叉树

  • 题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
原题链接

  • 思路

    之字形打印二叉树实际上和分层打印二叉树做法类似;

    我们可以想象一下,如果分层打印二叉树后偶数层不变,奇数层反转,是不是就等价于之字形打印二叉树了。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int>> Print(TreeNode* pRoot) {
        
        int d = dfs(pRoot);
        vector<vector<int>> res(d, vector<int>());
        if(!pRoot) return res;
        
        queue<pair<int, TreeNode*>> q;
        q.push({0, pRoot});
        while(!q.empty()) {
            
            auto t = q.front();
            q.pop();
            res[t.first].push_back(t.second->val);
            int l = t.first + 1;
            if(t.second->left) q.push({l, t.second->left});
            if(t.second->right) q.push({l, t.second->right});
        }
        
        for(int i = 0; i < d; i ++)
            if(i % 2)
                reverse(res[i].begin(), res[i].end());
                
        return res;
    }
    
    int dfs(TreeNode *root) {
        
        if(!root) return 0;
        return max(dfs(root->left), dfs(root->right)) + 1;
    }
};




14.序列化二叉树 🌟

  • 题目描述

请实现两个函数,分别用来序列化和反序列化二叉树。
你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
原题链接

  • 思路

    此题为二叉树的序列化和反序列化:

    • 序列化:将二叉树以某种方式转化为字符串;
    • 反序列化:将字符串表示的二叉树重新还原为二叉树。

    当然你可以采用各种各样的方式进行序列化,但实在没有必要搞出一种非常复杂的形式难为自己,我采用的是方式是这样的:前序遍历输出二叉树、每个节点用空格分隔、空节点用字符串 null 表示。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {

        string res;
        dfs_s(root, res);
        return res;   
    }

    void dfs_s(TreeNode *root, string &res) {

        if(!root) {
            
            res += "null ";
            return;
        }

        res += to_string(root->val) + ' ';
        dfs_s(root->left, res);
        dfs_s(root->right, res);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {

        int u = 0;
        return dfs_d(data, u);
    }

    TreeNode* dfs_d(string data, int &u) {

        //if(u == data.size()) return NULL;
        int k = u;
        while(data[k] != ' ') k ++;
        // null
        if(data[u] == 'n') {
            
            u = k + 1;
            return NULL;
        }

        // num
        int val = 0, sign = 1;
        if(data[u] == '-') sign = -1, u ++;
        for(int i = u; i < k; i ++) 
            val = val * 10 + data[i] - '0';
        val *= sign;
        u = k + 1;
        TreeNode *root = new TreeNode(val);
        root->left = dfs_d(data, u);
        root->right = dfs_d(data, u);
        return root;
    }
};




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值