LeetCode题目总结——树
1. 遍历
采用递归或非递归实现中序、前序、后序、层序遍历。
// 中序遍历
void inorder(vector<int>& res, TreeNode* root) {
if (root == nullptr) return;
inorder(res, root->left);
res.push_back(root->val);
inorder(res, root->right);
}
// 非递归
while (!stack.empty() || root != nullptr) {
while (root != nullptr) {
stack.push(root);
root = root->left;
}
root = stack.top();
stack.pop();
res.push_back(root->val);
root = root->right;
}
// 前序遍历
void preorder(vector<int>& res, TreeNode* root) {
if (root == nullptr) return;
res.push_back(root->val);
preorder(res, root->left);
preorder(res, root->right);
}
// 非递归1
while (!stack.empty() || root != nullptr) {
while (root != nullptr) {
res.push_back(root->val);
stack.push(root);
root = root->left;
}
root = stack.top();
stack.pop();
root = root->right;
}
// 非递归2
stack.push(root);
while (!stack.empty()) {
root = stack.top();
stack.pop();
res.push_back(root->val);
if (root->right) stack.push(root->right);
if (root->left) stack.push(root->left);
}
// 后序遍历
void postorder(vector<int>& res, TreeNode* root) {
if (root == nullptr) return;
postorder(res, root->left);
postorder(res, root->right);
res.push_back(root->val);
}
// 非递归
while (!stack.empty() || root != nullptr) {
while (root != nullptr) {
res.push_back(root->val);
stack.push(root);
root = root->right;
}
root = stack.top();
stack.pop();
root = root->left;
}
reverse(res.begin(), res.end());
// 非递归2
stack.push(root);
while (!stack.empty()) {
root = stack.top();
stack.pop();
res.push_back(root->val);
if (root->left) stack.push(root->left);
if (root->right) stack.push(root->right);
}
reverse(res.begin(), res.end());
// 层序遍历,非递归
if (root) q1.push(root);
while (!q1.empty()) {
q2.clear();
for (auto node : q1) {
res.push_back(node->val);
if (node->left) q2.push_back(node->left);
if (node->right) q2.push_back(node->right);
}
swap(q1, q2);
}
- 0094 - 二叉树的中序遍历
- 0098 - 验证二叉搜索树
- 0099 - 恢复二叉搜索树
中序遍历,寻找pre->val > cur->val的位置。
找到第一个时,p1 = pre, p2 = root;找到第二个时,p2 = root(第二个位置有可能找不到)。
最后交换p1与p2的值。 - 0102 - 二叉树的层序遍历
- 0103 - 二叉树的锯齿形层序遍历
- 0107 - 二叉树层次遍历 II
- 0116 - 填充每个节点的下一个右侧节点指针
可以借助next指针进行层序移动,实现O(1)的空间复杂度。
Node* leftmost = root;
// 若为最后一层则结束
while (leftmost->left) {
// 从该层最左侧节点开始,至该层最右侧节点
Node* head = leftmost;
while (head) {
// 左子节点指向右子节点
head->left->next = head->right;
// 右子节点指向下一节点的左子节点
if (head->next) {
head->right->next = head->next->left;
}
// 移动至下一节点
head = head->next;
}
// 向下移动一层
leftmost = leftmost->left;
}
7. 0117 - 填充每个节点的下一个右侧节点指针
与0116相同,可以借助next指针进行层序移动,实现O(1)的空间复杂度。由于不存在完全二叉树条件,所以每次需要动态记录当前遍历的前一个节点,以及当前层的首节点。
Node* leftmost = root;
while (leftmost) {
Node* pre = nullptr, next_leftmost = nullptr;
// 从该层最左侧节点开始,至该层最右侧节点
Node* head = leftmost;
while (head) {
// 遍历并处理左子节点
if (head->left) {
if (pre) pre->next = head->left; // 前一节点指向该节点
pre = head->left;
if (!next_leftmost) next_leftmost = head->left; // 若为该层第一个节点,则记录
}
// 遍历并处理左子节点
if (head->right) {
if (pre) pre->next = head->right; // 前一节点指向该节点
pre = head->right;
if (!next_leftmost) next_leftmost = head->right; // 若为该层第一个节点,则记录
}
// 移动至下一节点
head = head->next;
}
// 向下移动一层
leftmost = next_leftmost;
}
2. 根据遍历规则寻找前驱节点
遍历算法的特殊处理方法,不需要额外的栈存储,可以使空间复杂度从O(n)降低到O(1)。如Morris遍历。
- 0114 - 二叉树展开为链表
while (cur) {
if (cur->left) {
auto next = cur->left;
auto pre = next;
while (pre->right) {
pre = pre->right;
}
pre->right = cur->right;
cur->right = next;
cur->left = nullptr;
}
cur = cur->right;
}
3. 遍历序列反推二叉树
两种遍历序列,构造二叉树。
- 0105 - 从前序与中序遍历序列构造二叉树
前序 = { [root], [left part], [right part] }
中序 = { [left part], [root], [right part] }
根据前序遍历序列,寻找中序遍历的左右子树分界点(root),然后根据中序遍历的左右子树长度,寻找前序遍历的左右子树分节点,递归构建。
TreeNode* buildTree(vector<int>& preorder, int pre0, int pre1,
vector<int>& inorder, int in0, int in1) {
if (in1 == in0) return nullptr;
auto root = new TreeNode(preorder[pre0]);
int idx = inorder_idx_map[preorder[pre0]];
root->left = buildTree(preorder, pre0 + 1, pre0 + (idx - in0 + 1),
inorder, in0, idx);
root->right = buildTree(preorder, pre0 + (idx - in0 + 1), pre1,
inorder, idx + 1, in1);
return root;
}
- 0106 - 从中序与后序遍历序列构造二叉树
中序 = { [left part], [root], [right part] }
后序 = { [left part], [right part], [root] }
根据前序遍历序列,寻找后序遍历的左右子树分界点(root),然后根据中序遍历的左右子树长度,寻找后序遍历的左右子树分节点,递归构建。
TreeNode* buildTree(vector<int>& inorder, int in0, int in1,
vector<int>& postorder, int post0, int post1) {
if (in0 == in1) return nullptr;
int idx = inorder_idx_map[postorder[post1 - 1]];
auto root = new TreeNode(postorder[post1 - 1]);
root->left = buildTree(inorder, in0, idx,
postorder, post0, post0 + (idx - in0));
root->right = buildTree(inorder, idx + 1, in1,
postorder, post0 + (idx - in0), post1 - 1);
return root;
}
4. 自底向上生成
递归调用,自底向上生成,每个节点生成以当前节点为根节点的局部解,然后向上传递,上层结点统计左右节点的结果,并继续生成局部解并向上传递。
- 0095 - 不同的二叉搜索树 II
左右节点返回局部解Rl、Rr,当前节点将Rl、Rr依次组合,生成(Rl * Rr)个当前局部解。 - 0098 - 验证二叉搜索树
也可用中序遍历解决。 - 0100 - 相同的树
- 0101 - 对称二叉树
- 0104 - 二叉树的最大深度
int maxDepth(TreeNode* root) {
if (!root) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
- 0108 - 将有序数组转换为二叉搜索树
寻找中点构建当前树节点,中点左右两侧的数组分别递归构建左右子树。 - 0109 - 将有序链表转换为二叉搜索树
思路同题目0108,寻找中点的方式采用快慢指针。 - 0110 - 平衡二叉树
计算所有子树最大深度的同时,判断当前子树是否时平衡二叉树。
int maxDepth(TreeNode* root) {
if (!root) return 0;
int dl = maxDepth(root->left);
int dr = maxDepth(root->right);
if (dl < 0 || dr < 0 || abs(dl - dr) > 1) {
return -1;
}
return max(dl, dr) + 1;
}
- 0111 - 二叉树的最小深度
注意不能简单的将0104中的max改为min,只有叶节点才能返回有效深度,所以为nullptr的节点不能返回为0的深度值(除根节点)。
int minDepth(TreeNode* root) {
if (!root) return 0;
if (root->left && root->right) {
return min(minDepth(root->left), minDepth(root->right)) + 1;
}
if (root->left) {
return minDepth(root->left) + 1;
}
if (root->right) {
return minDepth(root->right) + 1;
}
else {
return 1;
}
}
- 0112 - 路径总和
bool hasPathSum(TreeNode* root, int sum) {
if (!root) return false;
if (!root->left && !root->right) {
return root->val == sum;
}
else {
return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
}
}
- 0124 - 二叉树中的最大路径和
生成当前节点的左右子树的最大单向路径和,
if (root->left)
l = max(0, search(res, root->left));
if (root->right)
r = max(0, search(res, root->right));
res = max(res, val + l + r);
return val + max(l, r);