综述
翻转二叉树:leetcode226
对称二叉树:leetcode101
二叉树的最大深度:leetcode104
二叉树的最小深度:leetcode111
完全二叉树的节点个数:leetcode222
平衡二叉树:leetcode110
二叉树的所有路径:leetcode257
左叶子之和:leetcode404
找树左下角的值:leetcode513
路径总和:leetcode112
从中序与后序遍历序列构造二叉树:leetcode106
最大二叉树:leetcode654
合并二叉树:leetcode617
二叉搜索树中的搜索:leetcode700
验证二叉搜索树:leetcode98
二叉搜索树的最小绝对差:leetcode530
二叉搜索树中的众数:leetcode501
二叉树的最近公共祖先:leetcode236
二叉搜索树的最近公共祖先:leetcode235
二叉搜索树中的插入操作:leetcode701
删除二叉搜索树中的节点:leetcode450
修剪二叉搜索树:leetcode669
将有序数组转换为二叉搜索树:leetcode108
把二叉搜索树转换为累加树:leetcode538
引言
二叉树理论
二叉树可分为:满二叉树、完全二叉树
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。如下图
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。(堆就是一棵完全二叉树)。完全二叉树如下图
二叉搜索树
二叉搜索树是一个有序树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
如下图:
平衡二叉搜索树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。如下图
C++中 map、set、multimap,multiset 的底层实现都是平衡二叉搜索树,所以 map、set 的增删操作时间时间复杂度是 logn,而 unordered_map、unordered_set 底层实现是哈希表。
二叉树存储方式及定义
二叉树可以链式存储,也可以顺序存储。如下两个图
用数组来存储二叉树遍历:如果父节点的数组下标是 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 traversal(TreeNode* cur, vector<int>& vec) {//1. 参数:传入vector来放节点的数值,返回类型:void,不用返回什么东西
if (cur == NULL) return;//2. 终止条件:遍历到空节点,则终止
//3. 单层递归逻辑
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
traversal(root, res);
return res;
}
};
中序遍历
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
后序遍历
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
二叉树统一迭代法
由于中序遍历 同时遍历节点和处理节点不一致的情况,而前序和后序没有这个情况
因此 将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标,用 NULL 来做标记。
并且由于遍历的顺序和处理的顺序不一样
比如:前序遍历是 中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子
先入栈右节点后左节点是因为这样取出来就是 先取左节点后右节点,所以处理的顺序就是 先左后右)
所以迭代法和递归法的顺序对应如下:
可以看出,迭代法 就是 递归法 的反序
中序遍历
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
if (st.top() != NULL) { //栈顶不为nullptr,就插入元素
TreeNode* node = st.top();
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> preorderTraversal(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;
}
};
后序遍历
class Solution {
public:
vector<int> postorderTraversal(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<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;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
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;
}
};
刷题总结
对于二叉树求深度和高度
使用前序求的是深度,后序求的是高度。
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度
一些刷题小tip
void backtracking(TreeNode* root, int targetSum) {
sum += root->val;//对于二叉树的回溯来说,一般中间处理的步骤在return之前
path.push_back(root->val);
if (root->left == nullptr && root->right == nullptr) {
if (sum == targetSum) res.push_back(path);
return;
}
}
- 遇到二叉搜索树,先考虑中序遍历
比如 验证二叉搜索树,这道题做了两三遍了还是没做出来 - 修剪二叉树 没做出来,需要后面再看看
- 平衡二叉树与完全二叉树的区别在于底层节点的位置,完全二叉树底层必须是从左到右连续的,且次底层是满的
- 堆是一棵完全二叉树,同时保证父子节点的顺序关系(有序)。 完全二叉树一定是平衡二叉树,堆的排序是父节点大于子节点,而搜索树是父节点大于左孩子,小于右孩子,所以堆不是平衡二叉搜索树。
- 二叉搜索树的增删改查:
增:二叉搜索树中的插入操作
删:删除二叉搜索树中的节点
改:修剪二叉搜索树
查:二叉搜索树中的搜索 、 二叉搜索树中的众数 、 二叉搜索树的最近公共祖先 - 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。
层序遍历十题练手
二叉树的层序遍历
二叉树的层序遍历 II
二叉树的右视图
二叉树的右视图
N 叉树的层序遍历
在每个树行中找最大值
填充每个节点的下一个右侧节点指针
填充每个节点的下一个右侧节点指针 II
二叉树的最大深度
二叉树的最小深度
翻转二叉树
题目
题解
层序遍历:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return nullptr;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
TreeNode* temp = cur->left;
cur->left = cur->right;
cur->right = temp;
if (cur->left != nullptr) que.push(cur->left);
if (cur->right != nullptr) que.push(cur->right);
}
}
return root;
}
};
递归遍历:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
traversal(root);
return root;
}
private:
void traversal(TreeNode* root) {
if (root == nullptr) return;
TreeNode* temp = root->left;
root->left = root->right;
root->right = temp;
traversal(root->left);
traversal(root->right);
}
};
对称二叉树
题目
题解
这道题是对比左右子树,而不是左右子节点
因此如果是使用递归法,需要同时递归左子树 和 右子树
如果使用迭代法,需要两个队列存放左子树 和 右子树
递归:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
return traversal(root->left, root->right);
}
private:
//1.返回值:bool,参数:两个指针,因为需要判断左右两棵树的值是否相等,所以一个指针遍历左子树,一个指针遍历右子树
bool traversal(TreeNode* leftTree, TreeNode* rightTree) {
//2.终止条件。左子树和右子树都为空,那么是true;左子树为空右子树不为空 或 左子树不为空右子树为空,那么是false
if (leftTree == nullptr && rightTree == nullptr) return true;
else if (leftTree == nullptr || rightTree == nullptr) return false;
//3.处理节点,值不等,false
if (leftTree->val != rightTree->val) return false;
//传左子树的左节点 和 右子树的右节点,比对外侧是否相等;同理内侧
bool outFlag = traversal(leftTree->left, rightTree->right);
bool inFlag = traversal(leftTree->right, rightTree->left);
return outFlag && inFlag;
}
};
层序迭代:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
//需要两个队列来分别存放左子树和右子树
queue<TreeNode*> queLeft;
queLeft.push(root->left);
queue<TreeNode*> queRight;
queRight.push(root->right);
while (!queLeft.empty() && !queRight.empty()) {
int sizeLeft = queLeft.size();
int sizeRight = queRight.size();
for (int i = 0; i < sizeLeft; i++) {
TreeNode* curLeft = queLeft.front();
queLeft.pop();
TreeNode* curRight = queRight.front();
queRight.pop();
//1.两个都为空 2.一个为空一个不为空 3.都不为空,需对比值
if (curLeft == nullptr && curRight == nullptr) continue;
if (curLeft == nullptr || curRight == nullptr) return false;
if (curLeft->val != curRight->val) return false;
//左子树先插左节点 右子树先插右节点
queLeft.push(curLeft->left);
queLeft.push(curLeft->right);
queRight.push(curRight->right);
queRight.push(curRight->left);
}
}
return true;
}
};
类似的题目
相同的树
相同的树 leetcode100
使用递归法,这道题和上道题一样,需要两个指针分别递归两个数进行对比
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) return true;
else if (p == nullptr || q == nullptr) return false;
if (p->val != q->val) return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
另一颗数的子树
另一棵树的子树 leetcode572
这道题很值得做一下
找子树,无非就是找 root 中是否有和 subRoot 一样的树,找的过程其实和 相同的树 leetcode100 问题是一样的
class Solution {
public:
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (root == nullptr && subRoot == nullptr) return true;
else if (root == nullptr || subRoot == nullptr) return false;
if (isSameTree(root, subRoot)) return true;//如果isSameTree是true,说明找到了,返回true
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);//如果isSameTree是false,说明没找到,需要找root的左子树有没有subRoot,找右子树有没有subRoot
}
private:
//就是 “ 相同的树 leetcode100 ” 的代码
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) return true;
else if (p == nullptr || q == nullptr) return false;
if (p->val != q->val) return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
};
二叉树的最大深度
题目
题解
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
而根节点的高度就是二叉树的最大深度,因此本题可以直接使用前序遍历找深度,或者后序遍历找根节点的高度
前序求深度:
class Solution {
public:
int maxDepth(TreeNode* root) {
traversal(root, 1);
return res;
}
private:
int res = 0;
void traversal(TreeNode* root, int depth) {
if (root == nullptr) return;
res = max(res, depth); //中
traversal(root->left, depth + 1); //左
traversal(root->right, depth + 1); //右
}
};
后序求根节点的高度:
class Solution {
public:
int maxDepth(TreeNode* root) {
return traversal(root);
}
private:
int traversal(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = traversal(root->left); //左
int rightDepth = traversal(root->right); //右
int depth = max(leftDepth, rightDepth) + 1;//中
return depth;
}
};
层序遍历:
class Solution {
public:
int maxDepth(TreeNode* root) {
int depth = 0;
if (root == nullptr) return depth;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (cur->left != nullptr) que.push(cur->left);
if (cur->right != nullptr) que.push(cur->right);
}
depth++;
}
return depth;
}
};
二叉树的最小深度
题目
题解
递归法:
求根节点到最近的叶子节点的高度就是最小深度,因此可以后序遍历
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
return traversal(root);
}
private:
int traversal(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = traversal(root->left); //左
int rightDepth = traversal(root->right);//右
//中
if (root->left == nullptr && root->right != nullptr) return rightDepth + 1;// 当一个左子树为空,右不为空,这时并不是最低点
if (root->left != nullptr && root->right == nullptr) return leftDepth + 1;// 当一个右子树为空,左不为空,这时并不是最低点
return min(leftDepth, rightDepth) + 1;
}
};
层序遍历:
class Solution {
public:
int minDepth(TreeNode* root) {
int depth = 0;
if (root == nullptr) return depth;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (cur->left == nullptr && cur->right == nullptr) return depth + 1;
if (cur->left != nullptr) que.push(cur->left);
if (cur->right != nullptr) que.push(cur->right);
}
depth++;
}
return 0;
}
};
完全二叉树的节点个数
题目
题解
按照普通二叉树遍历节点计算节点个数:
class Solution {
public:
int countNodes(TreeNode* root) {
tarversal(root);
return res;
}
private:
int res = 0;
void tarversal(TreeNode* root) {
if (root == nullptr) return;
res++;
tarversal(root->left);
tarversal(root->right);
}
};
时间复杂度:O(n)
按照满二叉树的计算公式去计算:
完全二叉树可以去找其子树是否是满二叉树,如果是满二叉树,节点个数就是:2^树深度 - 1
这样计算充分利用了完全二叉树的特征,速度更快
class Solution {
public:
int countNodes(TreeNode* root) {
return traversal(root);
}
private:
int traversal(TreeNode* root) {
if (root == nullptr) return 0;
TreeNode* leftNode = root->left;
TreeNode* rightNode = root->right;
int leftDepth = 0;
int rightDepth = 0;
//找左右子树的高度
while (leftNode != nullptr) {
leftNode = leftNode->left;
leftDepth++;
}
while (rightNode != nullptr) {
rightNode = rightNode->right;
rightDepth++;
}
//如果高度相等,说明从这个节点开始就是满二叉树,可以直接用公式计算;如果高度不相等,就递归
if (leftDepth == rightDepth) return (2 << leftDepth) - 1;//或者是pow(2, leftDepth + 1) - 1;
return traversal(root->left) + traversal(root->right) + 1;
}
};
时间复杂度:O(log n × log n),因为只需要遍历二叉树最左边和最右边的高度即可
代码中的(2 << leftDepth) - 1
可以替换成pow(2, leftDepth + 1) - 1
pow(2, leftDepth + 1)
和 (2 << leftDepth)
是等价的,只不过位运算速度更快。为什么pow(2, leftDepth + 1)
中需要加1呢,因为 leftDepth 只是左子树和右子树的高度,需要加上1也就是加上root节点,然后 2 的 leftDepth + 1 次方减去 1 就是节点数了
平衡二叉树
题目
题解
找左子树的高度 和 右子树的高度,进行对比,差1就不是平衡二叉树
找高度,说明是后序遍历
需要说明的一点是,层数遍历可以找深度,但是高度就很麻烦了,所以如果用迭代法求这道题的话,就用栈来模拟后序遍历实现
class Solution {
public:
bool isBalanced(TreeNode* root) {
if (root == nullptr) return true;
int res = traversal(root);
return res == -1 ? false : true;
}
private:
int traversal(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = traversal(root->left);
int rightDepth = traversal(root->right);
if (leftDepth < 0 || rightDepth < 0) return -1;//遇到左子树或右子树的高度为-1的,说明已经发现其不是平衡二叉树了,直接返回
if (leftDepth - rightDepth > 1 || rightDepth - leftDepth > 1) return -1;//高度差1,返回-1,说明不是平衡二叉树
return max(leftDepth, rightDepth) + 1;//高度没有差1,说明目前为止是平衡二叉树,返回最大高度
}
};
二叉树的所有路径
题目
题解
简单的回溯算法,但是需要注意的一点,回溯终止条件是到叶子节点,即root->left == nullptr && root->right == nullptr
而不是 root == nullptr
本题是求根节点到叶子节点的路径,所以是前序遍历
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
if (root == nullptr) return res;
backtracking(root);
return res;
}
private:
vector<string> res;
vector<int> path;
void backtracking(TreeNode* root) {
path.push_back(root->val);//中
if (root->left == nullptr && root->right == nullptr) {//必须是这样子才算到叶子节点,不能是 root == nullptr,这样子不一定到叶子节点
string temp;
for (int i = 0; i < path.size() - 1; i++) {
temp += to_string(path[i]);//需要注意一点,必须加上to_string
temp += "->";
}
temp += to_string(path.back());
res.push_back(temp);
return;
}
if (root->left != nullptr) {//左
backtracking(root->left);
path.pop_back();
}
if (root->right != nullptr) {//右
backtracking(root->right);
path.pop_back();
}
}
};
在代码中有个 temp += to_string(path[i]);
path[i] 是 int,加入到 string 类型的 temp,必须加 to_string,否则就是加的是 path[i] 的地址
左叶子之和
题目
题解
只需要知道左叶子节点的处理逻辑即可:if (root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr) {...}
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
traversal(root);
return res;
}
private:
int res = 0;
void traversal(TreeNode* root) {
if (root == nullptr) return;
if (root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr) res += root->left->val;
traversal(root->left);
traversal(root->right);
}
};
找树左下角的值
题目
题解
层序遍历:
这道题直接用层序是最简单的,再每一层都记录最左边的元素并覆盖上一层最左边的元素,那么最后返回的就是结果
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
que.push(root);
int res = 0;
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (i == 0) res = cur->val;//每一层都记录最左边的元素并覆盖上一层最左边的元素
if (cur->left != nullptr) que.push(cur->left);
if (cur->right != nullptr) que.push(cur->right);
}
}
return res;
}
};
递归法:
递归法的话就是要先找到最后一层,也就是深度最大的时候,找深度,那么需要前序遍历
维持一个“最大深度maxDepth”这个遍历,如果递归的深度 > maxDepth,那么就覆盖结果,最后找到的就是深度最大的结果
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
traversal(root, 0);
return res;
}
private:
int maxDepth = -1;
int res = 0;
void traversal(TreeNode* root, int depth) {
if (root == nullptr) return;
if (depth > maxDepth) {
maxDepth = depth;
res = root->val;
}
traversal(root->left, depth + 1);
traversal(root->right, depth + 1);
}
};
路径总和
题目
题解
如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回
所以本题的递归返回值是 bool
找从头到尾的路径,直接前序遍历
这个题需要有回溯,因为一条路径找不到需要回溯找另一条路径
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
res += root->val;
if (res == targetSum && root->left == nullptr && root->right == nullptr) return true;//叶子节点且和相等
if (root->left != nullptr) {//左
bool flag = hasPathSum(root->left, targetSum);
if (flag) return true;
res -= root->left->val;//回溯
}
if (root->right != nullptr) {//右
bool flag = hasPathSum(root->right, targetSum);
if (flag) return true;
res -= root->right->val;//回溯
}
return false;
}
private:
int res = 0;
};
路径综合Ⅱ
题目
题解
这道题和 路径总和 就不一样了,因为需要找到所有路径总和为 targetSum 的集合
那么就需要遍历整个二叉树,而不是找到一条就返回,因此返回值是 void
class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return res;
backtracking(root, targetSum);
return res;
}
private:
vector<vector<int>> res;
vector<int> path;
int sum = 0;
void backtracking(TreeNode* root, int targetSum) {
sum += root->val;//对于二叉树的回溯来说,一般中间处理的步骤在return之前
path.push_back(root->val);
if (root->left == nullptr && root->right == nullptr) {
if (sum == targetSum) res.push_back(path);
return;
}
if (root->left != nullptr) {//左
backtracking(root->left, targetSum);
sum -= root->left->val;//回溯
path.pop_back();
}
if (root->right != nullptr) {//右
backtracking(root->right, targetSum);
sum -= root->right->val;//回溯
path.pop_back();
}
}
};
从中序与后序遍历序列构造二叉树
题目
题解
后序数组的最后一个一定是中间节点,取出来,然后分割中序数组,分成左子树和右子树
按照中序数组分割出来的左子树和右子树的大小分割后序数组,也分成左子树和右子树,因为中序数组大小一定是和后序数组的大小相同的
如果提高效率,就不要分割数组,而是传递数组的索引,表示起始和终止点
传索引的时候需要注意要是“左闭右开”就都是“左闭右开”,要是“左闭右闭”就都是“左闭右闭”
这里选择左闭右开:
一开始中序数组的起始和终止是:inStart,inEnd
后序数组的起始和终止是:postStart,postEnd
- 那么先取出中间节点
TreeNode* midNode = new TreeNode(postorder[postEnd - 1]);
- 找到中间节点在中序数组的索引 midIndex
- 然后分割中序数组
左子树的起点和终点是:inStart,midIndex
右子树的起点和终点是:midIndex + 1,inEnd - 分割后序数组
左子树的起点是:postStart,找终点需要知道中序数组分出的左子树的长度:midIndex - inStart,不用减1,因为是左闭右开,不理解就举个例子动手试试。所以终点是:postStart + midIndex - inStart
右子树的起点是:postStart + midIndex - inStart,找中序数组的分出右子树的的长度:inEnd - midIndex - 1,所以后序右子树的终点是:postStart + midIndex - inStart + inEnd - midIndex - 1 即 postStart - inStart + inEnd - 1
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
return traversal(inorder, postorder, 0, inorder.size(), 0, postorder.size());
}
private:
TreeNode* traversal(vector<int>& inorder, vector<int>& postorder, int inStart, int inEnd, int postStart, int postEnd) {
if (inStart >= inEnd || postStart >= postEnd) return nullptr;//数组不存在,就返回 nullptr
TreeNode* midNode = new TreeNode(postorder[postEnd - 1]);//取中间节点
int midIndex = -1;//找中间节点的索引
for (int i = inStart; i < inEnd; i++) {
if (midNode->val == inorder[i]) {
midIndex = i;
break;
}
}
//递归左子树和右子树
midNode->left = traversal(inorder, postorder, inStart, midIndex, postStart, postStart + midIndex - inStart);
midNode->right = traversal(inorder, postorder, midIndex + 1, inEnd, postStart + midIndex - inStart, postStart - inStart + inEnd - 1);
return midNode;
}
};
从前序与中序遍历序列构造二叉树
leetcode105
类似后序和中序的组合,写出代码:
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return traversal(preorder, inorder, 0, preorder.size(), 0, inorder.size());
}
private:
TreeNode* traversal(vector<int>& preorder, vector<int>& inorder, int preStart, int preEnd, int inStart, int inEnd) {
if (preStart >= preEnd || inStart >= inEnd) return nullptr;
TreeNode* midNode = new TreeNode(preorder[preStart]);
int midIndex = -1;
for (int i = 0; i < inorder.size(); i++) {
if (midNode->val == inorder[i]) {
midIndex = i;
break;
}
}
midNode->left = traversal(preorder, inorder, preStart + 1, preStart + 1 + midIndex - inStart, inStart, midIndex);
midNode->right = traversal(preorder, inorder, preStart + 1 + midIndex - inStart, preEnd, midIndex + 1, inEnd);
return midNode;
}
};
需要注意的是前序和后序组合构造不出二叉树,因为没有中序不知道中间节点是哪个
最大二叉树
题目
题解
这道题也是分割数组,构建二叉树,类似于上一题
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums, 0, nums.size());
}
private:
TreeNode* traversal(vector<int>& nums, int start, int end) {
if (start >= end) return nullptr;//数组不存在,就返回nullptr
//找最大节点的索引值
int maxIndex = start;
for (int i = start; i < end; i++) {
if (nums[maxIndex] < nums[i]) maxIndex = i;
}
//生成节点,并递归左子树和右子树
TreeNode* maxNode = new TreeNode(nums[maxIndex]);
maxNode->left = traversal(nums, start, maxIndex);
maxNode->right = traversal(nums, maxIndex + 1, end);
return maxNode;
}
};
合并二叉树
题目
题解
同时遍历两棵树即可
前序、后序、中序遍历都可以
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
return traversal(root1, root2);
}
private:
TreeNode* traversal(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr && root2 == nullptr) return nullptr;
else if (root1 == nullptr) return root2;
else if (root2 == nullptr) return root1;
root1->val += root2->val;
root1->left = traversal(root1->left, root2->left);
root1->right = traversal(root1->right, root2->right);
return root1;
}
};
二叉搜索树中的搜索
题目
题解
class Solution {
public:
//按照二叉搜索树的性质做,效率提高。val小于节点值,找左子树;否则找右子树
TreeNode* searchBST(TreeNode* root, int val) {
if (root == nullptr) return nullptr;
if (root->val == val) return root;
else if (root->val > val) return searchBST(root->left, val);
else return searchBST(root->right, val);
}
};
用循环的做法的话就整个 while 循环,只要 root 不为空就一直循环,根据大小选择遍历左子树还是右子树
验证二叉搜索树
题目
题解
三刷这道题,还是踩坑了
对于二叉搜索树的验证,不能单纯的对比左节点小于中间节点,右节点大于中间节点就完事了
而是要对比左子树所有节点小于中间节点,右子树所有节点大于中间节点
所以其实只需要记得 二叉搜索树是有序的,中序遍历也是有序的,所以遇到二叉搜索树,先考虑中序遍历
可以中序遍历,用一个数组记录中序遍历的结果,然后判断数组是否有序
当然也可以直接中序遍历的时候就比较是否有序,这时候需要有一个 preNode 来记录之前遍历的节点:
class Solution {
public:
bool isValidBST(TreeNode* root) {
if (root == nullptr) return true;
bool leftFlag = isValidBST(root->left);//左
if (preNode == nullptr) preNode = root;//中
else {
if (preNode->val < root->val) preNode = root;
else return false;
}
bool rightFlag = isValidBST(root->right);//右
return leftFlag && rightFlag;
}
private:
TreeNode* preNode = nullptr;//记录之前的节点
};
迭代法的话就是用栈模拟中序遍历来做的
二叉搜索树的最小绝对差
题目
题解
这道题和 验证二叉搜索树 差不多
遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了
反正遇到二叉搜索树,直接中序遍历,然后一个 preNode 节点记录之前遍历的节点即可
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
if (root == nullptr) return 0;
getMinimumDifference(root->left);
if (preNode == nullptr) preNode = root;
else {
res = min(res, root->val - preNode->val);
preNode = root;
}
getMinimumDifference(root->right);
return res;
}
private:
TreeNode* preNode = nullptr;
int res = INT_MAX;
};
二叉搜索树中的众数
题目
题解
如果不是二叉搜索树,那就遍历整个数,用 map 记录各个节点的出现的次数,然后将 map 赋给 vector,对 vector 进行排序即可
如果是二叉搜索树,那直接中序遍历,梳理好 “中” 的处理逻辑即可
class Solution {
public:
vector<int> findMode(TreeNode* root) {
traversal(root);
return res;
}
private:
vector<int> res;
TreeNode* preNode = nullptr;
int maxNum = 0;
int nodeNum = 0;
void traversal(TreeNode* root) {
if (root == nullptr) return;
traversal(root->left);//左
//中
if (preNode == nullptr) {//前一个节点是空间点
preNode = root;
maxNum++;
nodeNum++;
res.push_back(root->val);
} else {//前一个节点不是空节点
if (root->val == preNode->val) nodeNum++;
else nodeNum = 1;
if (nodeNum > maxNum) {
res.clear();
res.push_back(root->val);
maxNum = nodeNum;
} else if (nodeNum == maxNum) res.push_back(root->val);
preNode = root;
}
traversal(root->right);//右
}
};
二叉树的最近公共祖先
题目
题解
这道题是求最近公共祖先,祖先是在 p 和 q 的上面的,所以是后序遍历
递归的左右需要用变量来接住,看看是否找到了
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == NULL) return NULL;
TreeNode* leftNode = lowestCommonAncestor(root->left, p, q); //左
TreeNode* rightNode = lowestCommonAncestor(root->right, p, q); //右
//中
if (leftNode != NULL && rightNode != NULL) return root;//说明找到了最近公共祖先
if (root->val == p->val || root->val == q->val) return root;//说明此节点是需要找的节点,返回
if (leftNode == NULL && rightNode != NULL) return rightNode;//右边找到了返回右边
if (leftNode != NULL && rightNode == NULL) return leftNode;//左边找到了返回左边
return NULL;//什么都没找到返回null
}
};
二叉搜索树的最近公共祖先
题目
题解
只要找到一个节点 root 满足 root->val >= minNode->val && root->val <= maxNode->val
那么就是最近公共祖先了
迭代法:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* cur = root;
TreeNode* minNode = p->val < q->val ? p : q;
TreeNode* maxNode = p->val < q->val ? q : p;
while (cur != NULL) {
if (cur->val >= minNode->val && cur->val <= maxNode->val) return cur;
else if (cur->val < minNode->val) cur = cur->right;
else cur = cur->left;
}
return NULL;
}
};
递归法:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* minNode = p->val < q->val ? p : q;
TreeNode* maxNode = p->val < q->val ? q : p;
return traversal(root, minNode, maxNode);
}
private:
TreeNode* traversal(TreeNode* root, TreeNode* minNode, TreeNode* maxNode) {
if (root == NULL) return NULL;
if (root->val >= minNode->val && root->val <= maxNode->val) return root;
else if (root->val < minNode->val) return traversal(root->right, minNode, maxNode);
return traversal(root->left, minNode, maxNode);
}
};
二叉搜索树中的插入操作
题目
题解
思路:找到一个节点是的 val < 节点值 并且 节点左节点是空的,就可以插入,如果是 val < 节点值 并且 节点左节点非空的,继续递归
同理 val > 节点值
迭代法:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
TreeNode* cur = root;
while (cur != nullptr) {
if (val < cur->val) {
if (cur->left == nullptr) {//val < 节点值 并且 节点左节点是空的,就可以插入
cur->left = new TreeNode(val);
return root;
} else cur = cur->left;//val < 节点值 并且 节点左节点非空的,继续递归
}
if (val > cur->val) {
if (cur->right == nullptr) {
cur->right = new TreeNode(val);
return root;
} else cur = cur->right;
}
}
return root;
}
};
递归法:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
traversal(root, val);
return root;
}
private:
TreeNode* traversal(TreeNode* root, int val) {//插入后就马上返回,不需要再递归了,所以需要返回值
if (root == nullptr) return nullptr;
if (val < root->val) {
if (root->left == nullptr) {//val < 节点值 并且 节点左节点是空的,就可以插入
root->left = new TreeNode(val);
return root;
} else return traversal(root->left, val);//val < 节点值 并且 节点左节点非空的,继续递归
}
if (val > root->val) {
if (root->right == nullptr) {
root->right = new TreeNode(val);
return root;
} else return traversal(root->right, val);
}
return nullptr;
}
};
删除二叉搜索树中的节点
题目
题解
这道题思路很简单,实现有点坑,需要动手试试
思路就是:找到要删除的节点的前一个节点 pre,将要删除的节点的右子树挂在左子树上
比如第一个示例:要删除 3,需要找到 5,然后将 3 的右子树 4 挂在左子树 2 上,用5连接2即可
需要注意的点就是:
- 可能找不到节点,那直接返回 root
- 可能要删除的就是 root,那么此时前节点 pre 是空的,需要将 root 的右子树挂在左子树,返回root->left 即可
- 找到了 pre,需要去看要删除的节点是 pre->left 还是 pre->right
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return nullptr;
bool isFind = traversal(root, key);
if (isFind == false) return root;//没找到key节点
if (isFind == true && pre == nullptr) {//说明key就是root这个节点
return mount(root->left, root->right);
} else {//说明key不是root节点,key的上一个节点是pre
if (pre->left != nullptr && pre->left->val == key) {//key是pre的左节点
pre->left = mount(pre->left->left, pre->left->right);
} else {//key是pre的右节点
pre->right = mount(pre->right->left, pre->right->right);
}
return root;
}
}
private:
TreeNode* pre = nullptr;
//找到key节点的上一个节点,方便进行删除操作,找到返回true,否则返回false
bool traversal(TreeNode* root, int key) {
if (root == nullptr) return false;
if (root->val == key) return true;
else if (key < root->val) {
pre = root;
return traversal(root->left, key);
}
else {
pre = root;
return traversal(root->right, key);
}
}
//右子树挂在左子树,返回左子树的首个节点
TreeNode* mount(TreeNode* leftNode, TreeNode* rightNode) {
if (leftNode == nullptr) return rightNode;
TreeNode* cur = leftNode;
while (cur->right != nullptr) {
cur = cur->right;
}
cur->right = rightNode;
return leftNode;
}
};
修剪二叉搜索树
题目
题解
这道题其实代码很简单,但是没做出来,因此需要注意
思路就是:
节点元素小于low,舍弃这个节点以及左子树,返回递归右子树
节点元素大于high,舍弃这个节点以及右子树,返回递归左子树
节点元素在 low 和 high 之间,就正常递归
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (root == nullptr) return nullptr;
if (root->val < low) return trimBST(root->right, low, high);//节点元素小于low,舍弃这个节点以及左子树,返回递归右子树
if (root->val > high) return trimBST(root->left, low, high);//节点元素大于high,舍弃这个节点以及右子树,返回递归左子树
root->left = trimBST(root->left, low, high);//节点元素在 low 和 high 之间,就正常递归
root->right = trimBST(root->right, low, high);
return root;
}
};
将有序数组转换为二叉搜索树
题目
题解
一直选数组中的中间节点作为当前节点,然后不断的递归左区间和右区间
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if (nums.size() == 0) return nullptr;
return traversal(nums, 0, nums.size() - 1);
}
private:
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left > right) return nullptr;
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;
}
};
把二叉搜索树转换为累加树
题目
题解
这道题只需要从后往前遍历累加即可,从后往前遍历就是 “右中左” 遍历
需要一个值来记录之前的累加值
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
traversal(root);
return root;
}
private:
int pre = 0;// 记录前一个节点的数值
void traversal(TreeNode* root) {
if (root == nullptr) return;
traversal(root->right);//右
root->val += pre;//中
pre = root->val;
traversal(root->left);//左
}
};