106. 从中序与后序遍历序列构造二叉树
以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
递归法
涉及到一层一层处理,肯定是要递归的,先捋一下思路,写个伪代码:
TreeNode* traversal(indorder, postorder) {
if (postorder.empty()) return nullptr; // 序列为空,直接返回空节点
// 将后序遍历的最后一个元素取出,为根结点
int rootValue = postorder[size - 1];
TreeNode* root = new TreeNode(rootValue);
// 在inorder上找分割点,用于将其分为左右两部分
int index;
for (index = 0; index < inorder.size(); index++)
if (inorder[index] == rootValue) break;
// 切割中序数组 左闭右开
// [0, index)
leftInorder = inorder(0, index);
// [index + 1, end)
rightInorder = inorder(index + 1, end);
// 切割后序数组 虽然元素位置不确定,但是后序子数组和中序子数组的长度是一样的
leftPostorder = inorder(0, index);
rightPostorder = inorder(index, end);
// 递归处理做数组和右数组
root->left = traversal(leftInorder, rightPostorder);
root->right = traversal(leftPostorder, rightInorder);
return root;
}
完整代码:
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (postorder.empty()) return nullptr;
// 取出根结点
int rootValue = *(postorder.end() - 1);
TreeNode* root = new TreeNode(rootValue);
// 寻找切割位置
int index = 0;
for(; index < inorder.size(); index++) {
if (inorder[index] == rootValue) break;
}
// 切割中序数组
vector<int> leftInorder(inorder.begin(), inorder.begin() + index);
vector<int> rightInorder(inorder.begin() + index + 1, inorder.end());
// 切割后序数组
vector<int> leftPostorder(postorder.begin(), postorder.begin() + index);
vector<int> rightPostorder(postorder.begin() + index, postorder.end() - 1);
//递归处理
root->left = buildTree(leftInorder, leftPostorder);
root->right = buildTree(rightInorder, rightPostorder);
return root;
}
};
上面的代码每次都考重新构造vector,浪费时间和空间,可以改成用下标来递归,这样比较快
class Solution {
public:
TreeNode* traversal(vector<int>& inorder, int iBegin, int iEnd, vector<int>& postorder, int pBegin, int pEnd) {
if (pBegin == pEnd) return nullptr;
// 取出根结点
int rootValue = postorder[pEnd - 1];
TreeNode* root = new TreeNode(rootValue);
if (pEnd - pBegin == 1) return root;
// 寻找切割位置
int index = iBegin;
for(; index < iEnd; index++) {
if (inorder[index] == rootValue) break;
}
//递归处理
root->left = traversal(inorder, iBegin, index, postorder, pBegin, pBegin + index - iBegin);
root->right = traversal(inorder, index + 1, iEnd, postorder, pBegin + index - iBegin, pEnd - 1);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
这个索引容易找错,草稿纸上多分析分析!!
可以看到用索引进行递归,在时间和空间上都要省不少。
迭代法
先空着
105. 从前序与中序遍历序列构造二叉树
递归法
这道题与上一道题一样,先序遍历的第一个元素一定是根结点,用它来将中序遍历分割为左右子树,然后递归划分,同样要注意索引的选取
class Solution {
public:
TreeNode* build(vector<int>& preorder, int pBeg, int pEnd, vector<int>& inorder, int iBeg, int iEnd) {
if (pBeg >= pEnd) {
return nullptr;
}
TreeNode* root = new TreeNode(preorder[pBeg]);
if (pBeg == pEnd - 1) {
return root;
}
int index = iBeg;
while (index < iEnd && inorder[index] != preorder[pBeg]) ++index;
root->left = build(preorder, pBeg + 1, pBeg + index - iBeg + 1, inorder, iBeg, index);
root->right = build(preorder, pBeg + index - iBeg + 1, pEnd, inorder, index + 1, iEnd);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return build(preorder, 0, preorder.size(), inorder, 0, inorder.size());
}
};
654. 最大二叉树
与从中序和后序(先序)遍历构造二叉树思路一样
- 递归的返回值和参数
构造一个数,所以肯定返回的是节点的指针
参数肯定要有数组nums
,因为要根据最大值将数组划分为两部分,所以递归的参数要有起始下标
- 终止条件
坚持左闭右开原则,如果起始下标重叠,返回nullptr
- 单层递归的逻辑
寻找最大元素的位置,然后用最大元素构造根结点,划分数组,递归构造左右子树
class Solution {
public:
TreeNode* traversal(vector<int>& nums, int begin, int end) {
if (begin == end)
return nullptr;
int max_index = begin;
for (int i = begin; i < end; i++) {
if (nums[max_index] < nums[i])
max_index = i;
}
TreeNode* root = new TreeNode(nums[max_index]);
root->left = traversal(nums, begin, max_index);
root->right = traversal(nums, max_index + 1, end);
return root;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums, 0, nums.size());
}
};
一次AC
617. 合并二叉树
递归法
- 递归的参数和返回值
返回树的节点,参数为两个节点
- 递归终止条件
如果两个节点都空,那么返回nullptr
- 单层递归逻辑
- 有一个节点为空,那么返回不空的那个节点(这样就不用再递归了,因为返回的那个节点相当于返回了子树)
- 两个节点都不空,构造新的节点,节点的值为两节点值之和
- 递归构造左子树
- 递归构造右子树
完整代码:(一遍AC)
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1 && !root2) return nullptr;
TreeNode* newRoot;
if (!root1)
newRoot = root2;
else if (!root2)
newRoot = root1;
else {
newRoot = new TreeNode(root1->val + root2->val);
newRoot->left = mergeTrees(root1->left, root2->left);
newRoot->right = mergeTrees(root1->right, root2->right);
}
return newRoot;
}
};
这个用的是前序遍历,中序和后序也是可以的,就是处理根结点的位置换一下
当然也可以不构造新的二叉树,直接把root1或者root2覆盖
迭代法
迭代法可以按照相同的树的做法,使用层序遍历来进行两根二叉树的合并
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1) return root2;
if (!root2) return root1;
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
while (!que.empty()) {
TreeNode* cur1 = que.front(); que.pop();
TreeNode* cur2 = que.front(); que.pop();
// 两节点一定都不为空
cur1->val += cur2->val;
// cur1左节点cur2的左节点都不为空,加入队列
if (cur1->left && cur2->left) {
que.push(cur1->left);
que.push(cur2->left);
}
// cur1右节点cur2的右节点都不为空,加入队列
if (cur1->right && cur2->right) {
que.push(cur1->right);
que.push(cur2->right);
}
// 第一根树为最后返回的树,因此只考虑cur1子树为空的情况,进行赋值,赋值后就不用入队再处理了
// cur1的左节点为空, cur2的左节点不为空
if (!cur1->left && cur2->left)
cur1->left = cur2->left;
// cur1的右节点为空, cur2的左节点不为空
if (!cur1->right && cur2->right)
cur1->right = cur2->right;
}
return root1;
}
};
好像还不如递归好用,重在理解思路