树问题的举例详解

本文介绍了树的相关概念和在LeetCode中的常见题型,包括使用递归进行树的比较、前序遍历、N叉树的前序遍历,以及树的序列化与反序列化。文章强调了前序遍历、层序遍历、后序遍历等不同遍历方法的应用,并提供了相应的代码实现。
摘要由CSDN通过智能技术生成

前言

        以下题目均来自力扣。刚系统的把树重新复习了一遍,所以有了这篇。

1.树与递归

        如果对于树不怎么熟悉,可以先写这里面的题目。

        100. 相同的树力扣

        给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

 

示例 1:

输入:p = [1,2,3], q = [1,2,3]
输出:true
示例 2:


输入:p = [1,2], q = [1,null,2]
输出:false
示例 3:


输入:p = [1,2,1], q = [1,1,2]
输出:false

        我们已经在第一篇里面讲过递归,其实就是操一致,这里其实不难发现,我们得先比较根节点,然后左子树,再然后右子树。代码如下。 

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

 其他都是这个思想只不过解决问题的步骤不一样。

题目 222、101、226、437、563、617、508、572、543、654、687、87

2.前序遍历

        144. 二叉树的前序遍历力扣

        给你二叉树的根节点 root ,返回它节点值的 前序 遍历

示例 1:

 

输入:root = [1,null,2,3]
输出:[1,2,3]
示例 2:

输入:root = []
输出:[]
示例 3:

输入:root = [1]
输出:[1]

        这个应该不难,只要了解前序遍历的规则就可以解决,代码如下。 

class Solution 
{
public:
    vector<int> preorderTraversal(TreeNode *root) 
    {
        vector<int> res;
        preorder(root, res);
        return res;
    }
    
    void preorder(TreeNode *root, vector<int> &res) 
    {
        if (root == nullptr) 
        {
            return;
        }
        res.push_back(root->val);
        preorder(root->left, res);
        preorder(root->right, res);
    }

};

         589. N 叉树的前序遍历力扣

        给定一个 n 叉树的根节点  root ,返回 其节点值的 前序遍历 。

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

示例 1:

输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
示例 2:

输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[1,2,3,6,7,11,14,4,8,12,5,9,13,10]

输入:root = [1,null,3,2,4,null,5,6]
输出:[1,3,5,6,2,4]
示例 2:

输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[1,2,3,6,7,11,14,4,8,12,5,9,13,10]

        思路不变,只是递归的时候不只是左右子树了,代码如下。

class Solution 
{
public:
    vector<int> preorder(Node* root) 
    {
        vector<int> res;
        dfs(root, res);
        return res;
    }

    void dfs(Node *root, vector<int> &res) 
    {
        if (root == nullptr) 
        {
            return;
        }
        res.push_back(root->val);
        for(int i = 0; i < root->children.size(); ++i)
            dfs(root->children[i], res);
    }
};

 3.前序遍历的序列化

        606. 根据二叉树创建字符串力扣

        给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。

空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。

示例 1:

 
输入:root = [1,2,3,4]
输出:"1(2(4))(3)"
解释:初步转化后得到 "1(2(4)())(3()())" ,但省略所有不必要的空括号对后,字符串应该是"1(2(4))(3)" 。
示例 2:


输入:root = [1,2,3,null,4]
输出:"1(2()(4))(3)"
解释:和第一个示例类似,但是无法省略第一个空括号对,否则会破坏输入与输出一一映射的关系。

         首先我们看到这个题得知道他为什么要让我们序列化出来,序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

        那么我们回到这个题,这里我们发现答案中无非就是根节点(不单单指root节点)在头,左右子节点如果存在就用括号括起来,没有就不用括号。那么这就是我写的第一篇文章dfs嘛,一直往下走,每一步操作一样,只是需要加入判断,判断左右节点是否存在。代码如下。

class Solution 
{
public:
    string str = "";
    string tree2str(TreeNode* root) 
    {
        dfs(root);
        return str;
    }
    
    void dfs(TreeNode* root)
    {
        if(root == nullptr) return;
        str += to_string(root->val);
        if(root->left == nullptr && root->right == nullptr) return;
        if(root->left != nullptr && root->right == nullptr)
        {
            str += "(";
            dfs(root->left);
            str += ")";
        }
        else 
        {
            str += "(";
            dfs(root->left);
            str += ")";
            str += "(";
            dfs(root->right);
            str += ")";
        }  
    } 
};

         这里面有一个问题,如果右节点存在,左节点不管存不存在都得有括号,所以出现了我写的if(root->left != nullptr && root->right == nullptr),这个就是左节点存在右节点不存在,所以只需要对左节点进行操作,其余除了左右都不存在以外都得加括号。

         297. 二叉树的序列化与反序列化力扣

         序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

        请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

        提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

示例 1:


输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
示例 2:

 

输入:root = []
输出:[]
示例 3:

输入:root = [1]
输出:[1]
示例 4:

输入:root = [1,2]
输出:[1,2]

         这个就是让我们自己定义一个方法如何序列化出来,然后下一次再用的时候可以反序列化。代码如下。

class Codec 
{
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) 
    {
        string str = "";
        rserialize(root, str);
        return str;
    }

    void rserialize(TreeNode* root, string& str) 
    {
        if(root == nullptr) 
        {
            str += "none,";
            return;
        }
        else
        {
            str += to_string(root->val) + ",";
            rserialize(root->left, str);
            rserialize(root->right, str);
        }
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data)
    {
        queue<string> q;
        string str = "";
        for(auto& s : data)
        {
            if(s == ',') 
            {
                q.push(str);
                str.clear();
            }
            else str.push_back(s);
        }
        if(!str.empty()) q.push(str);
        q.push(str);
        return rdeserialize(q);
    }

    TreeNode* rdeserialize(queue<string>& q)
    {
        if(q.front() == "none")
        {
            q.pop();
            return nullptr;
        }
        TreeNode *root = new TreeNode(stoi(q.front()));
        q.pop();
        root->left = rdeserialize(q);
        root->right = rdeserialize(q);
        return root;
    }
};

         这里我写的序列化也是前序遍历。还有类似的题目。

        331. 验证二叉树的前序序列化力扣

        652. 寻找重复的子树力扣

        449. 序列化和反序列化二叉搜索树力扣

4.层次遍历

        这一块的题目是最多的。

        直接说原理,层序遍历就是一层一层的遍历,用的是队列,这里也就是bfs,广度优先遍历,先前在第一篇文章里面没有赘述东西。

        102. 二叉树的层序遍历力扣

        给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:


输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:

 

输入:root = [1]
输出:[[1]]
示例 3:

输入:root = []
输出:[]

        没什么好说的,就是用队列的性质,直接代码。 

class Solution 
{
public:
    // 我一开始想的是用一个数字去记录2^x,但是这样会导致一个问题,空节点的时候就会出错
    // 所以直接用循环就可以了,一次性把所有的节点的下面节点都拿出来
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        if(root == nullptr) return vector<vector<int>> ();
        vector<vector<int>> res;
        vector<int> ans;
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty())
        {
            ans.clear();
            int n = q.size();
            for(int i = 0; i < n; ++i)
            {
                TreeNode* node = q.front(); q.pop();
                ans.emplace_back(node->val);
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            res.emplace_back(ans);
        }
        return res;
    }
};

         这一块题目虽然多,但是没什么太大的变化,基本都可以用上述代码解决,只不过要加一些其他的判断即可。类似题型

429、690、559、662、671、513、515、637、103、107、257、623、653、104、111、112、113、129、404、199、655、116、117

这里太多了就不给链接了。

5.后序遍历

        145. 二叉树的后序遍历力扣

        给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 

示例 1:


输入:root = [1,null,2,3]
输出:[3,2,1]
示例 2:

 

输入:root = []
输出:[]
示例 3:

输入:root = [1]
输出:[1]

         感觉没什么好说的。直接给代码。

class Solution 
{
public:
    void postorder(TreeNode *root, vector<int> &res) 
    {
        if (root == nullptr) 
        {
            return;
        }
        postorder(root->left, res);
        postorder(root->right, res);
        res.push_back(root->val);
    }

    vector<int> postorderTraversal(TreeNode *root) 
    {
        vector<int> res;
        postorder(root, res);
        return res;
    }
};

        590. N 叉树的后序遍历力扣

        给定一个 n 叉树的根节点 root ,返回 其节点值的 后序遍历 。

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

示例 1:

输入:root = [1,null,3,2,4,null,5,6]
输出:[5,6,3,2,4,1]
示例 2:

输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[2,6,14,11,7,3,12,8,4,13,9,10,5,1]

class Solution 
{
public:
    void postorderTraversal(Node *root, vector<int> &res) 
    {
        if (root == nullptr) 
        {
            return;
        }
        for(auto& a : root->children)
        {
            postorderTraversal(a, res);
        }
        res.emplace_back(root->val);
    }

    vector<int> postorder(Node *root) 
    {
        vector<int> res;
        postorderTraversal(root, res);
        return res;
    }
};

6.中序遍历

        这里都是在二叉搜索树的基础上进行,因为二叉搜索树的性质,中序遍历出来是递增有序的序列,反中序遍历就是递减有序序列。这里我也不在赘述了,其实都差不多。

题目有 94、700、530、538、230、98、173、669、450、110、95、108、109

7.总结

        树包含了许许多多的知识点,就比如dfs,bfs,动态规划,前缀和,这些都在树里面有所体现,所以树的题型必须得好好的刷一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值