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问题。
本周小结
- 五一期间开始理解树的相关题目,因为各种原因直到节后这一周才进行了回顾总结,期间还穿插了考试、实验和各种任务,因此本周的内容是对上周的复盘。
- 及时动态调整学习节奏,再接再厉。