二叉树 Binary Tree
二叉树 Binary Tree
常见种类
刷题时的常见二叉树:满二叉树和完全二叉树
满二叉树
如果一棵二叉树只有度为 0 的结点和度为 2 的结点,并且度为 0 的结点在同一层上,则这棵二叉树为满二叉树。
深度为k,有2^k-1
个节点。
完全二叉树
除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含1~2^h - 1
个节点。
优先级队列其实是一个堆,堆是一棵完全二叉树,同时保证父子节点的顺序关系。
二叉搜索树 BST
有序树
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树
比如AVL树、红黑树
-
它是⼀棵空树或它的左右两个⼦树的⾼度差的绝对值不超过1,并且左右两个⼦树都是⼀棵平衡⼆叉树。
-
C++中map、 set、 multimap, multiset 的底层实现都是平衡⼆叉搜索树,所以map、 set的增删操作时间复杂度是logn
-
注意 unordered_map、 unordered_set, unordered_map、unordered_map底层实现是哈希表。
二叉树的存储方式
链式存储 / 顺序存储
链式存储
使用指针,通过指针把分布在散落在各个地址的节点串联⼀起。
顺序存储
使用数组,顺序存储的元素在内存是连续分布的 。
父节点下标为i
,左节点为i*2+1
,右孩子为i*2+2
二叉树的定义
链式存储的二叉树节点的定义方式
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
二叉树的递归遍历
- 递归三要素
- 确定递归函数的参数和返回值
确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,
并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。 - 确定终止条件
写完了递归算法,运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。 - 确定单层递归的逻辑
确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程
- 确定递归函数的参数和返回值
遍历方式
深度优先遍历
前中后指的是中间节点的遍历顺序。
-
一般方法:递归、迭代
使用栈:后进先出
前序遍历
-
递归
class Solution { public: void PreOrderCore(TreeNode *cur, vector<int>& vec) { if(cur == NULL) return; vec.push_back(cur->val); PreOrderCore(cur->left, vec); PreOrderCore(cur->right, vec); } vector<int> PreOrder(TreeNode *Node) { vector<int> result; PreOrderCore(Node, result); return result; } }
-
迭代
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> result; if(root == NULL) return result; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); st.pop(); result.push_back(node->val); if(node->right) st.push(node->right); if(node->left) st.push(node->left); } return result; } };
中序遍历
-
递归
class Solution { public: void InOrderCore(TreeNode *cur, vector<int>& vec) { if(cur == NULL) return; InOrderCore(cur->left, vec); vec.push_back(cur->val); InOrderCore(cur->right, vec); } }
-
迭代
和前序遍历的思路不一样
- 中序遍历是左中右,先访问的是⼆叉树顶部的节点,然后⼀层⼀层向下访问,直到到达树左⾯的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不⼀致的。
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> result; TreeNode* cur = root; while(cur != NULL || !st.empty()) { if(cur != NULL) { st.push(cur); cur = cur->left; } else{ cur = st.top(); st.pop(); result.push_back(cur->val); cur = cur->right; } } return result; } };
后序遍历
-
递归
class Solution { public: void PostOrderCore(TreeNode *cur, vector<int>& vec) { if(cur == NULL) return; InOrderCore(cur->left, vec); InOrderCore(cur->right, vec); vec.push_back(cur->val); } }
-
迭代
先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转 result 数组,输出的结果顺序就是左右中了,如下图:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if(root == NULL)
return result;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if(node->left)
st.push(node->left);
if(node->right)
st.push(node->right);
}
reverse(result.begin(), result.end());
return result;
}
}
二叉树的统一迭代法
以中序遍历为例,如果使用栈,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
那么就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,即在要处理的节点放入栈之后,紧接放入一个空指针作为标记。
-
中序遍历:左中右
入栈顺序:右中左
class Solution { public: vector<int> InOrderTraversal(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if(root != NULL) st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node != NULL) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 if(node->right) // 添加右节点(空节点不入栈 st.push(node->right); st.push(node); // 添加中节点 st.push(NULL); // 中节点访问但未处理,加入空节点作为标记 if(node->left) // 添加左节点(空节点不入栈 st.push(node->left); } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.top(); // 重新取出栈中元素 st.pop(); result.push_back(node->val); // 加入到结果集 } } return result; } };
-
后序遍历:左右中
入栈顺序:中右左
class Solution { public: vector<int> InOrderTraversal(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if(root != NULL) st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node != NULL) { st.pop(); // 将该节点弹出,避免重复操作,下面再将右左中节点添加到栈中 // 只修改以下几步的步骤 st.push(node); // 中 st.push(NULL); if(node->right) // 右 st.push(node->right); if(node->left) // 左 st.push(node->left); // } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 st.pop(); // 将空节点弹出 node = st.top(); // 重新取出栈中元素 st.pop(); result.push_back(node->val); // 加入到结果集 } } return result; } };
-
前序遍历:中左右
入栈顺序:右左中
class Solution { public: vector<int> InOrderTraversal(TreeNode* root) { vector<int> result; stack<TreeNode*> st; if(root != NULL) st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node != NULL) { st.pop(); // 只修改以下几步的步骤 if(node->right) // 右 st.push(node->right); if(node->left) // 左 st.push(node->left); st.push(node); // 中 st.push(NULL); // } else { st.pop(); node = st.top(); st.pop(); result.push_back(node->val); } } return result; } };
广度优先遍历
-
一般方法:迭代
使用队列:先进先出
102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
class Solution {
public:
vector<vector<int>> LevelOrder(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL)
que.push(root);
vector<vector<int>> result;
while(!que.empty()) {
int size = que.size();
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size的大小是在不断变化的
vector<int> vec;
for(int i = 0; i < size; i ++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};
107. 二叉树的层序遍历Ⅱ
给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7], 3
/
9 20
/
15 7
返回其自底向上的层序遍历为:[
[15,7],
[9,20],
[3]
]
相对于102. 二叉树的层序遍历
,最后把result数组翻转一下
class Solution {
public:
vector<vector<int>> LevelOrder(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL)
que.push(root);
vector<vector<int>> result;
while(!que.empty()) {
int size = que.size();
vector<int> vec;
for(int i = 0; i < size; i ++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
result.push_back(vec);
}
reverse(result.begin(), result.end()); // 在这里翻转一下数组
return result;
}
};
199. 二叉树的右视图
给定一个二叉树的 根节点
root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
- 思路:
- 只须在层序遍历时,判断是否遍历到单层最后的元素了(即是否是第 size - 1 个元素)如果是就放进result数组中
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL)
que.push(root);
vector<int> result;
while(!que.empty()) {
int size = que.size();
vector<int> vec;
for(int i = 0; i < size; i ++) {
TreeNode* node = que.front();
que.pop();
if(i == size - 1) // 判断是否遍历到单层最后的元素
result.push_back(node->val);
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
}
return result;
}
};
637. 二叉树的层平均值
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
提示:
- 节点值的范围在32位有符号整数范围内。
- 思路:
- 在层序遍历的时候把一层求和再取均值
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL)
que.push(root);
vector<double> result;
while(!que.empty()) {
int size = que.size();
double sum = 0;
for(int i = 0; i < size; i ++) {
TreeNode* node = que.front();
que.pop();
sum += node->val;
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
result.push_back(sum/size);
}
return result;
}
};
429. N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔。
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> que;
if(root != NULL)
que.push(root);
vector<vector<int>> result;
while(!que.empty()) {
int size = que.size();
vector<int> vec;
for(int i = 0; i < size; i ++) {
Node* node = que.front();
que.pop();
vec.push_back(node->val);
for( int j = 0; j < node->children.size(); j ++ ) {
if(node->children[j]) que.push(node->children[j]);
}
}
result.push_back(vec);
}
return result;
}
};
515. 在每个树行中找最大值
给定一棵二叉树的根节点
root
,请找出该二叉树中每一层的最大值。
- 思路
- 层序遍历,取每一层的最大值
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL)
que.push(root);
vector<int> result;
while(!que.empty()) {
int size = que.size();
int maxValue = INT_MIN;
for(int i = 0; i < size; i ++) {
TreeNode* node = que.front();
que.pop();
maxValue = max(node->val, maxValue);
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
result.push_back(maxValue);
}
return result;
}
};
116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
- 你只能使用常量级额外空间。
- 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root != NULL)
que.push(root);
while(!que.empty()) {
int size = que.size();
Node* PreNode;
Node* node;
for(int i = 0; i < size; i ++) {
if(i == 0) {
PreNode = que.front();
que.pop();
node = PreNode;
}
else {
node = que.front();
que.pop();
PreNode->next = node;
PreNode = PreNode->next;
}
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
PreNode->next = NULL;
}
return root;
}
};
翻转二叉树
226. 翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/
2 7
/ \ /
1 3 6 9
输出: 4
/
7 2
/ \ /
9 6 3 1
-
思路:
-
递归
前序遍历,先翻转根节点的左右子树,然后以左子树为根向下翻转,再以右子树为根向下翻转。
递归三部曲:
-
确定递归函数的参数和返回值
参数:传入节点的指针
返回值:返回当前根节点的指针
TreeNode* invertTree(TreeNode* root)
-
确定终止条件
终止条件:传入节点为空
if(root == NULL) return root;
-
确定单层递归的逻辑
先翻转根节点的左右子树,然后以左子树为根向下翻转,再以右子树为根向下翻转。
swap(root->left, root->right); invertTree(root->left); invertTree(root->right);
代码实现:
class Solution { public: TreeNode* invertTree(TreeNode* root) { if(root == NULL) return root; swap(root->left, root->right); invertTree(root->left); invertTree(root->right); return root; } };
-
-
迭代
按之前的迭代模板,修改一点
class Solution { public: TreeNode* invertTree(TreeNode* root) { if(root == NULL) return root; stack<TreeNode*> st; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node != NULL) { st.pop(); if(node->right) st.push(node->right); if(node->left) st.push(node->left); st.push(node); st.push(NULL); } else { st.pop(); node = st.top(); st.pop(); swap(node->left, node->right); } } return root; } };
-
广度优先遍历
class Solution { public: TreeNode* invertTree(TreeNode* root) { if(root == NULL) return root; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); for( int i = 0; i < size; i ++ ) { TreeNode* node = que.front(); que.pop(); swap(node->left, node->right); if(node->left) que.push(node->left); if(node->right) que.push(node->right); } } return root; } };
-
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/
2 2
/ \ /
3 4 4 3但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/
2 2
\
3 3
-
思路:
-
递归
-
确定递归函数的参数和返回值
参数:左右子树的节点指针
返回值:bool值,左右子树是否对称
-
确定终止条件
返回false:
- 左右子树都不为空,数值不同
- 左子树为空,右子树不为空
- 右子树为空,左子树不为空
返回true
- 左右子树都不为空,数值相同
-
确定单层逻辑
此时左右子树都不为空且数值相等
判断左节点的左孩子和右节点的右孩子是否相等
以及,左节点的右孩子和右节点的左孩子是否相等
这两个情况必须都是true才能返回true,所以要相与
class Solution { private: bool compare(TreeNode* left, TreeNode* right) { if(left == NULL && right != NULL) return false; else if(left != NULL && right == NULL) return false; else if(left == NULL && right == NULL) return true; else if(left->val != right->val) return false; return compare(left->left, right->right) && compare(left->right, right->left); } public: bool isSymmetric(TreeNode* root) { if(root == NULL) return true; return compare(root->left, root->right); } };
-
-
迭代
- 用队列
class Solution { public: bool isSymmetric(TreeNode* root) { if(root == NULL) return true; queue<TreeNode*> que; que.push(root->left); que.push(root->right); while(!que.empty()) { TreeNode* LeftNode = que.front(); que.pop(); TreeNode* RightNode = que.front(); que.pop(); if(!LeftNode && !RightNode) continue; if(!LeftNode || !RightNode || (LeftNode->val != RightNode->val) ) return false; que.push(LeftNode->left); que.push(RightNode->right); que.push(LeftNode->right); que.push(RightNode->left); } return true; } };
- 用栈
class Solution { public: bool isSymmetric(TreeNode* root) { if(root == NULL) return true; stack<TreeNode*> st; st.push(root->right); st.push(root->left); while(!st.empty()) { TreeNode* LeftNode = st.top(); st.pop(); TreeNode* RightNode = st.top(); st.pop(); if(!LeftNode && !RightNode) continue; if(!LeftNode || !RightNode || (LeftNode->val != RightNode->val)) return false; st.push(RightNode->right); st.push(LeftNode->left); st.push(RightNode->left); st.push(LeftNode->right); } return true; } };
-
二叉树的最大深度
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7], 3
/
9 20
/
15 7
返回它的最大深度 3 。
-
思路:
-
递归
class Solution { public: int maxDepth(TreeNode* root) { if(root == NULL) return 0; return max(maxDepth(root->left), maxDepth(root->right)) + 1; } };
-
迭代
层序遍历查找最大深度
class Solution { public: int maxDepth(TreeNode* root) { if(root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth ++; for( int i = 0; i < size; i ++ ) { TreeNode* node = que.front(); que.pop(); if(node->left) que.push(node->left); if(node->right) que.push(node->right); } } return depth; } };
-
559. N叉树的最大深度
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
-
思路:
-
递归
和二叉树递归的思路相同
class Solution { public: int maxDepth(Node* root) { if(root == NULL) return 0; int depth = 0; for( int i = 0; i < root->children.size(); i ++ ) depth = max(depth, maxDepth(root->children[i])); return 1 + depth; } };
-
迭代
依旧是层序遍历
class Solution { public: int maxDepth(Node* root) { if(root == NULL) return 0; int depth = 0; queue<Node*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth ++; for( int i = 0; i < size; i ++ ) { Node* node = que.front(); que.pop(); for( int j = 0; j < node->children.size(); j ++ ) if(node->children[j]) que.push(node->children[j]); } } return depth; } };
-
二叉树的最小深度
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
-
递归
-
确定递归函数的参数和返回值
int GetDepth(TreeNode* node)
-
确定终止条件
if (node == NULL) return 0;
-
确定单层递归逻辑
**注意:**最小深度指的是从根节点到最近叶子节点的最短路径上的节点数量,而叶子节点是指没有子节点的节点,如果当前节点为空但其父节点的另一个孩子节点不为空,则其父节点不能算是叶子节点。
- 当左子树节点为空,右子树节点不为空时。返回最小深度为
1 + 右子树深度
- 当右子树节点为空,左子树节点不为空时。返回最小深度为
1 + 左子树深度
- 当左右子树节点都不为空,返回
左右子树深度的最小值 + 1
int leftDepth = getDepth(node->left); int rightDepth = getDepth(node->right); if(node->left == NULL && node->right != NULL) return 1 + getDepth(node->right); if(node->right == NULL && node->left != NULL) return 1 + getDepth(node->left); int result = 1 + min(leftDepth, rightDepth); return result;
重点: 处理左右子树不为空的逻辑
- 当左子树节点为空,右子树节点不为空时。返回最小深度为
-
代码:
class Solution { private: int getDepth(TreeNode* root) { if (root == NULL) return 0; int rightDepth = getDepth(root->right); int leftDepth = getDepth(root->left); if (root->left == NULL && root->right != NULL) return 1 + rightDepth; if (root->right == NULL && root->left != NULL) return 1 + leftDepth; int result = 1 + min(rightDepth, leftDepth); return result; } public: int minDepth(TreeNode* root) { return getDepth(root); } };
精简:
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right != NULL) return 1 + minDepth(root->right); if (root->right == NULL && root->left != NULL) return 1 + minDepth(root->left); return 1 + min(minDepth(root->left), minDepth(root->right)); } };
-
-
迭代
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth ++; for (int i = 0; i < size; i ++) { TreeNode* node = que.front(); que.pop(); if(node->left) que.push(node->left); if(node->right) que.push(node->right); if(!node->left && !node->right) return depth; } } return depth; } };
完全二叉树的节点个数
222. 完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含
1~ 2^h
个节点。
-
思路:
-
递归
普通二叉树的遍历方式
以当前节点为根的二叉树节点个数 = 1 + 左子树节点个数 + 右子节点个数
class Solution { public: int countNodes(TreeNode* root) { if (root == NULL) return 0; return 1 + countNodes(root->left) + countNodes(root->right); } };
-
迭代
普通二叉树迭代
class Solution { public: int countNodes(TreeNode* root) { if (root == NULL) return 0; queue<TreeNode*> que; que.push(root); int result = 0; while(!que.empty()) { int size = que.size(); for (int i = 0; i < size; i ++) { TreeNode* node = que.front(); que.pop(); result ++; if(node->left) que.push(node->left); if(node->right) que.push(node->right); } } return result; } };
-
完全二叉树的方法
上面两种都是普通二叉树的解法,并没有用上完全二叉树的特性
完全二叉树:
-
满二叉树:节点数 = 2^树深度-1
-
最后一层叶子节点没有满:分别递归左孩子和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
-
-
```C++
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == NULL) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int LeftNodes = 0, RightNodes = 0;
while (left) {
left = left->left;
LeftNodes ++;
}
while (right) {
right = right->right;
RightNodes ++;
}
if (LeftNodes == RightNodes)
return (2 << LeftNodes) - 1;
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
```
平衡二叉树
110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
- 思路
-
递归
-
确定递归函数的参数和返回值
返回值为以传入节点为根节点的树的深度
如果不是平衡二叉树,则返回 -1
int getDepth(TreeNode* node)
-
明确终止条件
if (node == NULL) return 0;
-
明确单层递归的逻辑
int LeftDepth = getDepth(node->left); if (LeftDepth == -1) return -1; int RightDepth = getDepth(node->right); if (RightDepth == -1) return -1; return abs(LeftDepth - RightDepth) > 1? -1: 1 + max(LeftDepth, RightDepth);
-
代码
class Solution { private: int getDepth(TreeNode* node) { if (node == NULL) return 0; int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; int rightDepth = getDepth(node->right); if (rightDepth == -1) return -1; return abs(leftDepth - rightDepth) > 1? -1: 1 + max(leftDepth, rightDepth); } public: bool isBalanced(TreeNode* root) { return getDepth(root) == -1? false: true; } };
-
-
二叉树路径
递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112. 路径总和)
257. 二叉树的所有路径
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]
示例 2:输入:root = [1]
输出:[“1”]
-
思路:
- 前序遍历递归 + 回溯
class Solution { public: vector<string> binaryTreePaths(TreeNode* root) { vector<string> result; if (root == NULL) return result; vector<int> path; traversal(root, path, result); return result; } private: void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) { // 前序遍历 path.push_back(cur->val); // 终止条件:找到叶子节点,即cur不为空,左右孩子节点为空。 if (!cur->left && !cur->right) { string sPath; for (int i = 0; i < path.size() - 1; i ++) { sPath += to_string(path[i]); sPath += "->"; } sPath += to_string(path[path.size() - 1]); result.push_back(sPath); return; } if (cur->left) { traversal(cur->left, path, result); path.pop_back(); // 回溯 } if (cur->right) { traversal(cur->right, path, result); path.pop_back(); // 回溯 } } };
517. 找树左下角的值
给定一个二叉树的 根节点
root
,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7
-
思路:
-
递归:
遍历整个树找到最大深度,然后使用前序遍历(这样才能优先左边搜索), 然后记录深度最大的叶子节点
-
找最大深度
-
确定递归的参数和返回值
无返回值
-
确定终止条件
当遇到叶子节点的时候,就需要记录一下深度,所以需要遇到叶子节点来更新最大深度。
-
确定单层递归逻辑
回溯遍历左/右节点
class Solution { public: int findBottomLeftValue(TreeNode* root) { Find_Max_Depth(root, 0); return DEPTHEST_LEFT; } private: int MAX_DEPTH = INT_MIN; int DEPTHEST_LEFT; // 记录最大深度最左边的值 void Find_Max_Depth(TreeNode* node, int LeftDepth) { if (!node->left && !node->right) { DEPTHEST_LEFT = LeftDepth > MAX_DEPTH? node->val: DEPTHEST_LEFT; MAX_DEPTH = max(MAX_DEPTH, LeftDepth); return; } if (node->left) Find_Max_Depth(node->left, LeftDepth + 1); if (node->right) Find_Max_Depth(node->right, LeftDepth + 1); return; } };
-
-
-
迭代
层序遍历,返回最后一层的第一个节点。
class Solution { public: int findBottomLeftValue(TreeNode* root) { queue<TreeNode*> que; que.push(root); int result = 0; while (!que.empty()) { int que_size = que.size(); for (int i = 0; i < que_size; i ++) { TreeNode* cur = que.front(); que.pop(); if (i == 0) result = cur->val; if (cur->left) que.push(cur->left); if (cur->right) que.push(cur->right); } } return result; } };
-
构造二叉树
106. 从中序和后序遍历构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
-
思路:
- 如果数组大小为0的话,说明是空节点了
- 如果数组不为空,取后序数组的最后一个元素作为节点元素
- 找到后序数组最后一个元素在中序数组中的位置,作为切割点
- 切割中序数组,切成中序左数组和中序右数组
- 切割后序数组,切成后序左数组和后续右数组
- 递归处理左区间和右区间
class Solution { private: TreeNode* traversal(vector<int>& inorder, vector<int>& postorder) { // 第一步:如果数组大小为零的话,说明是空节点了 if (!postorder.size() || !inorder.size()) return NULL; // 第二步:如果不为空的话,取后序数组最后一个元素作为节点元素 TreeNode* root = new TreeNode(postorder[postorder.size() - 1]); if (postorder.size() == 1) return root; // 第三步:找到后序数组最后一个元素在中序数组中的位置,作为切割点 int CutPoint = 0; for (; CutPoint < inorder.size(); CutPoint ++) { if (inorder[CutPoint] == root->val) break; } // 第四步:切割中序数组 // 左闭右开区间:[0, CutPoint) vector<int> LeftInorder(inorder.begin(), inorder.begin() + CutPoint); vector<int> RightInorder(inorder.begin() + CutPoint + 1, inorder.end()); // 第五步:切割后序数组(按照后序左右数组和中序左右数组的大小一样决定) postorder.resize(postorder.size() - 1); vector<int> LeftPostorder(postorder.begin(), postorder.begin() + LeftInorder.size()); vector<int> RightPostorder(postorder.begin() + LeftInorder.size(), postorder.end()); // // 一下为日志 // cout << "----------" << endl; // cout << CutPoint << endl; // cout << "leftInorder :"; // for (int i : LeftInorder) { // cout << i << " "; // } // cout << endl; // cout << "rightInorder :"; // for (int i : RightInorder) { // cout << i << " "; // } // cout << endl; // cout << "leftPostorder :"; // for (int i : LeftPostorder) { // cout << i << " "; // } // cout << endl; // cout << "rightPostorder :"; // for (int i : RightPostorder) { // cout << i << " "; // } // cout << endl; // 第六步:递归处理左区间和右区间 root->left = traversal(LeftInorder, LeftPostorder); root->right = traversal(RightInorder, RightPostorder); return root; } public: TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { if (!inorder.size() || !postorder.size()) return NULL; return traversal(inorder, postorder); } };
-
优化
因为每次递归都新建了vector,会增加很多内存开销。
class Solution { private: TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) { // 第一步:如果数组大小为零的话,说明是空节点了 if (inorderBegin == inorderEnd || postorderBegin == postorderEnd) return NULL; // 第二步:如果不为空的话,取后序数组最后一个元素作为节点元素 TreeNode* root = new TreeNode(postorder[postorderEnd - 1]); if (postorderEnd - postorderBegin == 1) return root; // 第三步:找到后序数组最后一个元素在中序数组中的位置,作为切割点 int CutPoint = 0; for (; CutPoint < inorder.size(); CutPoint ++) { if (inorder[CutPoint] == root->val) break; } // 第四步:切割中序数组 // 左闭右开区间:[LeftInorderBegin, LeftInorderEnd) int LeftInorderBegin = inorderBegin; int LeftInorderEnd = CutPoint; int RightInorderBegin = CutPoint + 1; int RightInorderEnd = inorderEnd; // 第五步:切割后序数组(按照后序左右数组和中序左右数组的大小一样决定) int LeftPostorderBegin = postorderBegin; int LeftPostorderEnd = postorderBegin + CutPoint - inorderBegin; int RightPostorderBegin = postorderBegin + CutPoint - inorderBegin; int RightPostorderEnd = postorderEnd - 1; // // 以下为日志 // cout << "----------" << endl; // cout << CutPoint << endl; // cout << "leftInorder :"; // for (int i : LeftInorder) { // cout << i << " "; // } // cout << endl; // cout << "rightInorder :"; // for (int i : RightInorder) { // cout << i << " "; // } // cout << endl; // cout << "leftPostorder :"; // for (int i : LeftPostorder) { // cout << i << " "; // } // cout << endl; // cout << "rightPostorder :"; // for (int i : RightPostorder) { // cout << i << " "; // } // cout << endl; // 第六步:递归处理左区间和右区间 root->left = traversal(inorder, LeftInorderBegin, LeftInorderEnd, postorder, LeftPostorderBegin, LeftPostorderEnd); root->right = traversal(inorder, RightInorderBegin, RightInorderEnd, postorder, RightPostorderBegin, RightPostorderEnd); return root; } public: TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { if (!inorder.size() || !postorder.size()) return NULL; return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size()); } };
106. 从前序与中序遍历构造二叉树
654. 最大二叉树
以上两个题都和105一个思路
合并二叉树
617. 合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入: Tree 1 Tree 2 1 2 / \ / \ 3 2 1 3 / \ \ 5 4 7 输出: 合并后的树: 3 / \ 4 5 / \ \ 5 4 7
注意: 合并必须从两个树的根节点开始。
-
思路:
-
在原有树上直接修改,使用前中后序遍历都可以。
class Solution { public: TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) { if (!root1) return root2; if (!root2) return root1; root1->val += root2->val; root1->left = mergeTrees(root1->left, root2->left); root1->right = mergeTrees(root1->right, root2->right); return root1; } };
-
二叉搜索树BST
700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,
给定二叉搜索树:
4 / \ 2 7 / \ 1 3
和值: 2
你应该返回如下子树:2 / \ 1 3
在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。
- 第一思路:直接递归:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root) return NULL;
if (root->val == val) return root;
TreeNode* left = searchBST(root->left, val);
if (left) return left;
TreeNode* right = searchBST(root->right, val);
if (right) return right;
return NULL;
}
};
- 莽撞了,没用上BST特性,再优化一下
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root || root->val == val) return root;
if (root->val > val) return searchBST(root->left, val);
if (root->val < val) return searchBST(root->right, val);
return NULL;
}
};
- 迭代一下
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while (root != NULL) {
if (root->val > val) root = root->left;
else if (root->val < val) root = root->right;
else return root;
}
return NULL;
}
};
98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
-
思路:
-
将整个BST中序遍历转换成一个数组,判断数组是否是有序的。
class Solution { private: void InOrderTraversal(TreeNode* node, vector<int>& values) { if (!node) return; if (node->left) InOrderTraversal(node->left, values); values.push_back(node->val); if (node->right) InOrderTraversal(node->right, values); } public: bool isValidBST(TreeNode* root) { if (root == NULL) return false; vector<int> values; InOrderTraversal(root, values); int val = values[0]; for (int i = 1; i < values.size(); i ++) { if (val >= values[i]) return false; val = values[i]; } return true; } };
-
在递归遍历的过程中直接判断是否有序
-
陷阱1!
不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。
例如: [10,5,15,null,null,6,20] 这个case:
-
陷阱2
最小节点可能是int的最小值,如果这样使用最小的int来比较也是不行的。
所以可以初始化比较元素为longlong的最小值。
class Solution { public: long long MAX_VAL = LONG_MIN; bool isValidBST(TreeNode* root) { if (root == NULL) return true; bool left = isValidBST(root->left); if (MAX_VAL < root->val) MAX_VAL = root->val; else return false; bool right = isValidBST(root->right); return left && right; } };
-
-
530. 二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
-
思路:
-
中序遍历记录当前节点和前一个节点的差值,并更新最小差值。
- 递归
class Solution { int MIN_DIFF = INT_MAX; TreeNode* PreNode; void InOrderTraversal(TreeNode* root) { if (root == NULL) return; InOrderTraversal(root->left); if (PreNode != NULL) { MIN_DIFF = min(MIN_DIFF, root->val - PreNode->val); } PreNode = root; InOrderTraversal(root->right); return; } public: int getMinimumDifference(TreeNode* root) { if (root == NULL) return 0; InOrderTraversal(root); return MIN_DIFF; } };
- 迭代
class Solution { public: int getMinimumDifference(TreeNode* root) { stack<TreeNode*> st; TreeNode* cur = root; TreeNode* pre = NULL; int MIN_DIFF = INT_MAX; while (cur != NULL || !st.empty()) { if (cur != NULL) { st.push(cur); cur = cur->left; } else { cur = st.top(); st.pop(); if (pre != NULL) MIN_DIFF = min(MIN_DIFF, cur->val - pre->val); pre = cur; cur = cur->right; } } return MIN_DIFF; } };
-
501. 二叉搜索树中的众数
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],1 \ 2 / 2
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
**进阶:**你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
-
思路:
-
中序遍历一遍
- 递归
class Solution { private: int MAX_FREQ, Cur_Freq; TreeNode* PreNode; vector<int> result; void InOrderTraversal(TreeNode* cur) { if (!cur) return; InOrderTraversal(cur->left); if (PreNode == NULL) Cur_Freq = 1; else if (PreNode->val == cur->val) Cur_Freq ++; else Cur_Freq = 1; // 注意这里不能先判断大于再判断等于! if (Cur_Freq == MAX_FREQ) result.push_back(cur->val); if (Cur_Freq > MAX_FREQ) { MAX_FREQ = Cur_Freq; result.clear(); result.push_back(cur->val); } PreNode = cur; InOrderTraversal(cur->right); return; } public: vector<int> findMode(TreeNode* root) { MAX_FREQ = 0; Cur_Freq = 0; PreNode = NULL; result.clear(); InOrderTraversal(root); return result; } };
- 迭代
class Solution { public: vector<int> findMode(TreeNode* root) { vector<int> result; if (!root) return result; stack<TreeNode*> st; int Max_Count = 0; int Cur_Count = 0; TreeNode* PreNode = NULL; TreeNode* cur = root; while (cur != NULL || !st.empty()) { if (cur != NULL) { st.push(cur); cur = cur->left; } else { cur = st.top(); st.pop(); if (PreNode == NULL) // First Node Cur_Count = 1; else if (PreNode->val == cur->val) Cur_Count ++; else Cur_Count = 1; if (Cur_Count == Max_Count) result.push_back(cur->val); if (Cur_Count > Max_Count) { Max_Count = Cur_Count; result.clear(); result.push_back(cur->val); } PreNode = cur; cur = cur->right; } } return result; } };
-
公共祖先
236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
-
思路:
-
如果能自底向上查找就好了:回溯啊,二叉树回溯的过程就是从低到上。
后序遍历就是天然的回溯过程,最先处理的一定是叶子节点。
-
如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
那么先用left和right接住左子树和右子树的返回值,代码如下:
TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q);
如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解
如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。
class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == p || root == q || root == NULL) return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left != NULL && right != NULL) return root; else if (left == NULL && right != NULL) return right; else if (left != NULL && right == NULL) return left; else return NULL; } };
-
235. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
-
思路:
要用到BST的特点
class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root->val < p->val && root->val < q->val) return lowestCommonAncestor(root->right, p, q); else if (root->val > p->val && root->val > q->val) return lowestCommonAncestor(root->left, p, q); else return root; } };
左右别搞反!!
701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
-
思路:
-
只需要找到一个合适的空节点然后插入就好了
class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { if (root == NULL) { TreeNode* cur = new TreeNode(val); return cur; } if (root->val > val) root->left = insertIntoBST(root->left, val); if (root->val < val) root->right = insertIntoBST(root->right, val); return root; } };
-
450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。
-
这里就把平衡二叉树中删除节点遇到的情况都搞清楚。
有以下五种情况:
- 第一种情况:没找到删除的节点,遍历到空节点直接返回了
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
// 1. 没找到删除的节点,遍历到空节点直接返回了
if (root == NULL) return NULL;
// 找到删除的节点
if (root->val == key) {
// 2. 左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
if (root->left == NULL && root->right == NULL)
return NULL;
// 3. 删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
else if (root->left == NULL) return root->right;
// 4. 删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
else if (root->right == NULL) return root->left;
// 5. 左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
else {
// 找右子树最左节点
TreeNode* RightChild_Leftest = root->right;
while (RightChild_Leftest->left)
RightChild_Leftest = RightChild_Leftest->left;
// 将要删除节点的左子树放在右子树的最左节点的左孩子处
RightChild_Leftest->left = root->left;
// 把root保存一下,后面删除
TreeNode* tmp = root;
// 返回root的右子树作为新root
root = root->right;
delete tmp;
return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
};
669. 修剪二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
示例 1:
输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]
-
思路:
- 注意!一个节点在边界外时,不能直接把其中一个子树挪过来,因为子树不一定全在边界内!
class Solution { public: TreeNode* trimBST(TreeNode* root, int low, int high) { if (root == NULL) return NULL; if (root->val < low) { TreeNode* right = trimBST(root->right, low, high); return right; } if (root->val > high) { TreeNode* left = trimBST(root->left, low, high); return left; } root->left = trimBST(root->left, low, high); root->right = trimBST(root->right, low, high); return root; } };
108. 将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
-
思路:
-
和106.从中序与后序遍历序列构造二叉树等其他构造二叉树的思路基本一样
class Solution { TreeNode* BuildBST(vector<int>& nums, int Left_Bound, int Right_Bound) { // 1. 数组里没有元素 if (Right_Bound - Left_Bound == 0) return NULL; // 2. 数组里有元素 // 建立根节点 int Root_Index = (Right_Bound + Left_Bound)/2; TreeNode* root = new TreeNode(nums[Root_Index]); // 如果数组里只有一个根节点,就返回根节点 if (Right_Bound - Left_Bound == 1) return root; // 3. 分割左右区间,左闭右开 int Left_begin = Left_Bound; int Left_end = Root_Index; int Right_begin = Root_Index + 1; int Right_end = Right_Bound; // 4. 连接左右子节点 root->left = BuildBST(nums, Left_begin, Left_end); root->right = BuildBST(nums, Right_begin, Right_end); return root; } public: TreeNode* sortedArrayToBST(vector<int>& nums) { if (nums.size() == 0) return NULL; return BuildBST(nums, 0, nums.size()); } };
-
538. 把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
-
思路:
-
思路1:
- 中序遍历整棵树,得到总累加值
- 然后再中序遍历整棵树更新节点值
-
思路2:
不要遍历两遍,按右中左的顺序从右往左遍历累加,只需要遍历一遍
(反中序遍历)
class Solution { private: int pre; void Traversal(TreeNode* cur) { if (cur == NULL) return; Traversal(cur->right); cur->val += pre; pre = cur->val; Traversal(cur->left); return; } public: TreeNode* convertBST(TreeNode* root) { pre = 0; Traversal(root); return root; } };
-