C++后端开发学习日记(第五周)

2022.5.1 day 17

东哥带你刷二叉树(构造篇)

本篇主要采用分解问题的思路:二叉树 = 根节点 + 左子树 + 右子树

654.最大二叉树

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。

分析:

每个二叉树节点都可以认为是一棵子树的根节点,对于根节点,首先要做的当然是把想办法把自己先构造出来,然后想办法构造自己的左右子树。

所以,我们要遍历数组把找到最大值 maxVal,从而把根节点 root 做出来,然后对 maxVal 左边的数组和右边的数组进行递归构建,作为 root 的左右子树。

解答:

class Solution {
public:
    TreeNode* build(vector<int>& nums, int low, int high) {
        if (low > high) return nullptr;
        int index = low, max = -1;
        for (int i = low; i <= high; i++) {
            if (nums[i] > max) {
                max = nums[i];
                index = i;
            }
        }
        TreeNode* root = new TreeNode(max);
        root->left = build(nums, low, index - 1);
        root->right = build(nums, index + 1, high);
        return root;
    }
    //
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return build(nums, 0, nums.size() - 1);
    }
};

切记“循环不变量原则”。建议采用[low,high],坚持左闭右闭的原则进行编码。需要注意以下四个部分:

  • 初始的边界条件应设置为low > high,因为相等的情况是有意义的
  • 在找最大值的for循环内的终止条件应设置为i <= high
  • 左右子树递归调用函数的左右下标为index - 1和index + 1
  • 主函数调用的下标是0和size() - 1

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

思路:

前序的根结点把中序分为左右两个子树,在两个子树再递归构造子树即可。理解好这个图就可以做了。

在这里插入图片描述
在这里插入图片描述

注意:

  • 本题为了根据中序的根节点位置,在前序数组求左右子树的分界点,引入了leftSize的变量,用于记录左子树的个数

  • 寻找索引若采用哈希法则可以做到优化

  • 如何确定控制数组起始位置的索引?画图即可解决!

代码:

class Solution {
public:
    TreeNode* build(vector<int>& preorder, int preStart, int preEnd, 
    vector<int>& inorder, int inStart, int inEnd) {
        if (preStart >preEnd) return nullptr;
        //在中序中找索引
        int index = 0;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == preorder[preStart]) {
                index = i;
                break;
            }
        }
        int leftSize = index - inStart;//左子树的节点数
        //前序第一个值为根节点
        TreeNode* root = new TreeNode(preorder[preStart]);
        //中序下标好确定
        root->left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1);
        root->right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() == 1 || inorder.size() == 1) 
            return (new TreeNode(preorder[0]));
        return build(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);   
    }
};

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

思路同上。注意确定索引的形式,后序的最后一个元素是根节点。

在这里插入图片描述

class Solution {
public:
    TreeNode* build(vector<int>& inorder, int inStart, int inEnd, 
    vector<int>& postorder, int postStart, int postEnd) {
        if (inStart > inEnd) return nullptr;
        //在中序中找索引
        int index = 0;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == postorder[postEnd]) {
                index = i;
                break;
            }
        }
        int leftSize = index - inStart;//左子树的节点数
        //后序,最后一个一个值为根节点
        TreeNode* root = new TreeNode(postorder[postEnd]);
        //下标确定
        root->left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1);
        root->right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 1 || postorder.size() == 1) 
            return (new TreeNode(inorder[0]));
        return build(inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1);     
    }
};

889.根据前序和后序遍历序列构造二叉树

前序知识:

  • 前序中序、中序后序遍历构造二叉树

  • 须知此题结果不唯一,因为int leftRootVal = preorder[preStart + 1];这一句,自然地将根节点的其后一个元素作为根节点的左子树,而实际上左子树可能为空

思路:

用后序遍历和前序遍历结果还原二叉树,解法逻辑上和前两道题差别不大,也是通过控制左右子树的索引来构建:

1、首先把前序遍历结果的第一个元素或者后序遍历结果的最后一个元素确定为根节点的值

2、然后把前序遍历结果的第二个元素作为左子树的根节点的值

3、在后序遍历结果中寻找左子树根节点的值,从而确定了左子树的索引边界,进而确定右子树的索引边界,递归构造左右子树即可

在这里插入图片描述

代码:

用for遍历索引位置(注释部分是用unordered_map哈希索引进行改进)

class Solution {
public:
    //unordered_map<int, int> ValtoIndex;
    TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
        /*
        for(int i = 0; i < postorder.size(); i++){
            ValtoIndex.emplace(postorder[i], i);
        }
        */
        return build(preorder, 0, preorder.size() - 1, postorder, 0, postorder.size() - 1);

    }
    TreeNode* build(vector<int>& preorder, int preleft, int preright, vector<int>& postorder, int postleft, int postright){
        if(preleft > preright)
            return nullptr;
        if(preleft == preright)
            return new TreeNode(preorder[preleft]);
        int rootval = preorder[preleft];
        //不唯一性
        int leftrootval = preorder[preleft + 1];
        //int index = ValtoIndex[leftrootval];
        int index = 0;
        for (int i = postleft; i <= postright; i++) {
            if (leftrootval == postorder[i]) {
                index = i;
                break;
            }
        }
        int leftsize = index - postleft + 1;
        TreeNode* root = new TreeNode(rootval);
        root->left = build(preorder, preleft + 1, preleft + leftsize, postorder, postleft, index);
        root->right = build(preorder, preleft + leftsize + 1, preright, postorder, index + 1, postright - 1);
        return root;
    }
};

每日小结

  • 本节是分解法解决二叉树问题的进一步应用,主要用于二叉树的构造。之前学习数据结构只是徒手还原二叉树,经过学习,理解这一过程的代码实现,并明白核心是坚持“循环不变量”原则和通过画图确定数组的索引。

2022.5.2 day 18

东哥带你刷二叉树(序列化篇)

297.二叉树的序列化与反序列化 Hard

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

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

以目前的能力写不出来,现列于此,作为引申思考。

前序知识:

  • 二叉树的层次遍历
  • 二叉树的构造

层次遍历序列化:

class Codec {
public:
    string data;
    // Encodes a tree to a single string.
    // 将树序列化为字符串
    string serialize(TreeNode* root) {
        if (root == nullptr) 
            return "";
        string str;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            if (node)
                str += (to_string(node->val) + ",");
            else {
                str += "#,";
                continue;
            }
            q.push(node->left);
            q.push(node->right);
        }
        str.pop_back();
        return str;
    }
	//切分字符串
    vector<string> split(string& str) {
        vector<string> ans;
        if (str.empty()) return ans;
        int size = str.size();
        int pre = 0;
        for (int i = 0; i <= size; i++) {
            if (i == size || str[i] == ',') {
                ans.emplace_back(str.substr(pre, i - pre));
                pre = i + 1;
            }
        }
        return ans;
    }
	//将字符串还原为树,由于包含了null的信息,因此结果具有唯一性
    TreeNode* deserialize(vector<string>& data) {
        if (data.empty())   return nullptr;
        string node = data[0];

        TreeNode* root = new TreeNode(stoi(node));
        queue<TreeNode*> q;
        q.push(root);

       for(int i =1; i < data.size(); ) {
            TreeNode* node = q.front();
            q.pop();
            if (node == nullptr)    continue;

            string s_left = data[i++];
            if (s_left == "#")
                node->left = nullptr;
            else
                node->left = new TreeNode(stoi(s_left));
            q.push(node->left);

            string s_right = data[i++];
            if (s_right == "#")
                node->right = nullptr;
            else
                node->right = new TreeNode(stoi(s_right));
            q.push(node->right);
        }
        return root;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        vector<string> iters = split(data);
        return deserialize(iters);
    }
};

每日小结

  • 遇到的第一个hard难题,先不要慌,仔细分析可以看出,其实本体规模和解决思路可以分解成三个easy/medium问题。

本周小结

  • 五一期间开始理解树的相关题目,因为各种原因直到节后这一周才进行了回顾总结,期间还穿插了考试、实验和各种任务,因此本周的内容是对上周的复盘。
  • 及时动态调整学习节奏,再接再厉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值