关于二叉树的一些算法题
关于树的一些算法题
106. 从中序与后序遍历序列构造二叉树
105. 从前序与中序遍历序列构造二叉树
算法逻辑
-
前序和中序遍历的特点
- 前序遍历:根 -> 左 -> 右
- 中序遍历:左 -> 根 -> 右
- 后序遍历:左 -> 右 -> 根
- 通过前序遍历的第一个节点确定根节点,然后在中序遍历中找到根节点的位置,分割左子树和右子树。
- 通过后序遍历的最后一个节点确定根节点,然后在中序遍历中找到根节点的位置,分割右子树和左子树。
-
递归步骤
A. 前序、中序构造
- 从前序遍历取当前根节点,创建节点。
- 在中序遍历中找到根节点位置
rootIndex
,从而确定左子树范围[inLeft, rootIndex - 1]
和右子树范围[rootIndex + 1, inRight]
。 - 递归构造左子树和右子树,更新前序遍历索引。
B. 后序、中序构造
- 从后序遍历取当前根节点,创建节点。
- 在中序遍历中找到根节点位置
rootIndex
,从而确定左子树范围[inLeft, rootIndex - 1]
和右子树范围[rootIndex + 1, inRight]
。 - 递归构造右子树和左子树,更新前序遍历索引。
-
哈希表优化
- 预处理中序遍历,构建值到索引的映射,快速定位根节点。
时间和空间复杂度
- 时间复杂度 O(n)
- 哈希表构建:O(n)。
- 递归构造树:每个节点访问一次,O(n)。
- 空间复杂度 O(n)
- 哈希表:O(n)。
- 递归调用栈:O(h),h 为树高,最坏情况下为 O(n)。
构造左右子树的顺序分析
- 前序和中序遍历的特点
- 前序遍历:根 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根 -> 右子树
- 在前序遍历中,根节点是第一个节点(
preorder[preIndex]
)。找到中序遍历中根节点的位置后,左子树的节点在前序遍历中紧跟根节点,因此先递归构造左子树,再构造右子树。
- 后序和中序遍历的特点
- 后序遍历:左子树 -> 右子树 -> 根
- 中序遍历:左子树 -> 根 -> 右子树
- 在后序遍历中,根节点是最后一个节点(
postorder[postIndex]
)。找到中序遍历中根节点的位置后,右子树的节点在后序遍历中位于根节点之前,且右子树的根节点是倒数第二个节点(或更靠前的节点,取决于左子树的大小)。因此,需要先递归构造右子树,再构造左子树。
- 构造顺序的差异
- 在前序和中序构造中,
preIndex
从前往后递增,左子树的节点在前序遍历中先于右子树,因此先构造左子树。 - 在后序和中序构造中,
postIndex
从后往前递减,右子树的节点在后序遍历中先于左子树(但在根之前),因此先构造右子树。
- 在前序和中序构造中,
代码
class Solution {
public:TreeNode* _buildTree(vector<int>& preorder, int& pos, vector<int>& inorder, int left, int right, unordered_map<int, int>& indexMap) {if(left > right) return nullptr;TreeNode* root = new TreeNode(preorder[pos]);int index = indexMap[preorder[pos]];pos++;root->left = _buildTree(preorder, pos, inorder, left, index - 1, indexMap);root->right = _buildTree(preorder, pos, inorder, index + 1, right, indexMap);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {unordered_map<int, int> indexMap;for(int i = 0;i < inorder.size();i++)indexMap[inorder[i]] = i;int rootpos = 0;return _buildTree(preorder, rootpos, inorder, 0, inorder.size() - 1, indexMap);}
};
class Solution {
public:TreeNode* _buildTree(vector<int>& postorder, int& pos, vector<int>& inorder, int left, int right, unordered_map<int, int>& indexMap) {if(left > right) return nullptr;if(pos < 0) return nullptr;TreeNode* root = new TreeNode(postorder[pos]);int index = indexMap[postorder[pos]];pos--;root->right = _buildTree(postorder, pos, inorder, index + 1, right, indexMap);root->left = _buildTree(postorder, pos, inorder, left, index - 1, indexMap);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {unordered_map<int, int> indexMap;for(int i = 0;i < inorder.size();i++)indexMap[inorder[i]] = i;int rootpos = postorder.size() - 1;return _buildTree(postorder, rootpos, inorder, 0, inorder.size() - 1, indexMap);}
};
102. 二叉树的层序遍历
关键:levelSize记录每层非空结点个数
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {if(root == nullptr) return vector<vector<int>>();queue<TreeNode*> q;q.push(root);int levelSize = 1;vector<vector<int>> vv;while(!q.empty()){ vector<int> v;for(int i = 0;i < levelSize;i++){TreeNode* cur = q.front();q.pop();v.push_back(cur->val);if(cur->left != nullptr) q.push(cur->left);if(cur->right != nullptr) q.push(cur->right);}levelSize = q.size();vv.push_back(v);}return vv;}
};