(一)理论基础
(1)种类:
-
满二叉树 :节点数量为 2^k - 1
-
完全二叉树:除了底层外,其它层都是满的,但是底层节点必须从左到右是连续的
-
二叉搜索树:搜索的时间复杂度为logn级别,左子树所有节点都小于根节点,右子树所有节点都大于根节点
-
平衡二叉搜索树:左子树和右子树高度差的绝对值不能超过1(map,set,multimap,multiset底层实现都是平衡二叉搜索树,是有序的;unordered_set,unordered_map底层实现为哈希表,是无序的)
(2)存储方式:
-
链式存储:每个节点都包括左指针、右指针(更一般)
-
线性存储:用字符数组保存二叉树(标号为每一层从左到右,自上而下进行数字标号)
a b c d e f g 0 1 2 3 4 5 6 元素 i 的左右孩子分别是2*i+1, 2*i + 2
(3)二叉树的遍历
-
深度优先搜索:递归实现,迭代法也可以(前序/后序/中序遍历),一直搜到终点,回退,再换条路搜索
-
前序/后序/中序遍历(中间节点的遍历顺序)
前序遍历:中-左-右
中序遍历:左-中-右
后序遍历:左-右-中
-
广度优先搜索:一层一层遍历(层序遍历),迭代法,队列实现
(4)二叉树的定义
链表存储
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(NULL), right(NULL){}
};
(二)二叉树的递归遍历
前序/中序/后序遍历
递归三步分析:
step1: 确定递归函数的参数和返回值
参数:根节点、数组(存放遍历结果)
返回值:一般为void
step2: 确定终止条件
遇到空节点时,递归终止
step3: 确定单层递归的逻辑
前序:中-左-右(取出根节点,递归遍历左子树,递归遍历右子树)
中序:左-中-右(递归遍历左子树,取出根节点,递归遍历右子树)
后序:左-右-中(递归遍历左子树,递归遍历右子树,取出根节点)
//cur为根节点,vec存放遍历结果
void traversal(cur, vec){
if(cur == NULL){ //终止条件
return;
}
vec.push(cur -> val); //放入根节点
traversal(cur -> left, vec); //遍历左子树
traversal(cur -> right, vec); //遍历右子树
}
void traversal(cur, vec){
if(cur == NULL){ //终止条件
return;
}
traversal(cur -> left, vec); //遍历左子树
vec.push(cur -> val); //放入根节点
traversal(cur -> right, vec); //遍历右子树
}
void traversal(cur, vec){
if(cur == NULL){ //终止条件
return;
}
traversal(cur -> left, vec); //遍历左子树
traversal(cur -> right, vec); //遍历右子树
vec.push(cur -> val); //放入根节点
}
class Solution {
public:
void traversal(TreeNode* root, vector<int>& vec){
if(root == NULL){
return;
}
vec.push_back(root -> val);
traversal(root -> left, vec);
traversal(root -> right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
(三)二叉树的迭代遍历
使用栈实现非递归遍历
先处理中间节点,再将右节点入栈,再做节点(出栈顺序)
逻辑:
①前序遍历
step1: 放入根节点
step2: 弹出一个元素,将它的右节点、左节点先后放入
step3: 重复第二步,直到栈为空
void<int> function(root){
stack<node> it;
vector<int> vec; //存放遍历的结果
it.push(root);
while(!st.empty()){
//1.取出一个节点
node = st.top();
st.pop();
//2.判断取出的节点是否为空,不为空则放入结果数组(中)
if(node != NULL) vec.push(node -> val)
else continue;
//3.将取出节点的右、左孩子分别入栈
st.push(node -> right); //(右)
st.push(node -> left); //(左)
}
return vec;
}
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
st.push(root);
TreeNode* temp;
while(!st.empty()){
temp = st.top();
st.pop();
if(temp != NULL){
result.push_back(temp -> val);
}else{
continue;
}
st.push(temp -> right);
st.push(temp -> left);
}
return result;
}
};
②后序遍历
前序 :中-左-右 → 中-右-左 → 左-中-右(后序)
后序 :左-右-中
void<int> function(root){
stack<node> it;
vector<int> vec; //存放遍历的结果
it.push(root);
while(!st.empty()){
//1.取出一个节点
node = st.top();
st.pop();
//2.判断取出的节点是否为空,不为空则放入结果数组(中)
if(node != NULL) vec.push(node -> val)
else continue;
//3.将取出节点的右、左孩子分别入栈
st.push(node -> left); //(左)
st.push(node -> right); //(右)
}
return vec.reverse; //反转数组
}
③中序遍历
访问节点、处理节点
对于前序遍历(中-左-右):访问顺序和处理(加入数组)顺序一致
中序(左-中-右),访问顺序和处理顺序不一致
一路向左,不断压栈,直到找到叶子节点为止,将其出栈,放入数组
将中间节点出栈,放入数组
将右孩子入栈……、
用指针遍历节点,用栈记录遍历过的节点,再按照顺序从栈中弹出元素放入数组
vector<int> tranversal(root){
vector<int> result;
stack<node*> st; //用栈来存储指针的位置
node* cur = root;
//注意这里是或的关系,就是栈为空同时指针指向e
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:
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;
}
};
(四)二叉树的层序遍历
广度优先遍历:队列实现
记录当前层的大小size,控制每层从队列中弹出几个元素
当上一层元素全部弹出后,更新size大小,栈里面剩余元素数量就是当前层的元素数量。通过while循环控制将该层全部弹出。
queue<TreeNode*> que;
if(root != NULL) que.push(root);
result;//二维数组
while(!que.empty()){
//上一层遍历完,记录当前层节点的个数
size = que.size();
vector<int> vec; // 记录当前层所有节点
//弹出当前层的所有元素。
//因为que.size是不断变化的,因此要先记录当前层所有节点数
while(size--){
node = que.front();
que.pop();
vec.push_back(node -> value);
// 将出栈元素的左右节点入栈
if(node -> left) que.push(node -> left);
if(node -> right) que.push(node -> right);
}
result.push_back(vec); //将当前层节点放入二维数组
}
return result;
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
vector<int> vec;
while(size --){
TreeNode* top = que.front();
que.pop();
vec.push_back(top -> val);
if(top -> left) que.push(top -> left);
if(top -> right) que.push(top -> right);
}
result.push_back(vec);
}
return result;
}
};
拓展:
①二叉树的最大深度
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 ++;
while(size --){
TreeNode* node = que.front();
que.pop();
if(node -> left) que.push(node -> left);
if(node -> right) que.push(node -> right);
}
}
return depth;
}
};
②二叉树的最小深度
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL){
return 0;
}
if(root -> left == NULL) return minDepth(root -> right) + 1;
if(root -> right == NULL) return minDepth(root -> left) + 1;
return min(minDepth(root -> left),minDepth(root -> right)) + 1;
}
};
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL){
return 0;
}
int depth = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
depth ++;
int size = que.size();
while(size --){
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;
}
};
(五)反转二叉树
交换指针,不是单纯交换值
前序较为方便
TreeNode* invertTree(root){
if(root == NULL) return root;
// 处理中间节点,交换其左右孩子指针
swap(root -> left, root -> right);
invertTree(root -> left);
invertTree(root -> right);
}
不要使用中序遍历,较为麻烦。
左子树交换了之后,到了右子树,又处理一遍,而右子树没有处理。
(六)对称二叉树
左右子树能否相互反转
确定遍历方式:只能使用后序(左-右-中)
左边的孩子处理完了,右边的孩子处理完了,收集左右孩子的信息,向上面的节点返回结果。
终止条件
左 | 右 | 返回结果 |
---|---|---|
空 | 不为空 | false |
不为空 | 空 | false |
空 | 空 | true |
不为空 | 不为空 | (值不等)false |
不为空 | 不为空 | (值相等)继续向下一层递归遍历 |
//判断左右子树是否可以反转
bool compare(TreeNode* left, right){
// 终止条件
if(left == NULL && right != NULL) false;
else if(left != NULL && right == NULL) false;
else if(left == NULL && right == NULL) true;
else if(left -> val != right -> val) false;
// 处理单层递归逻辑,对左子树来说,是左-右-中处理逻辑,对应后序遍历
// 比较左右子树的外侧节点是否相等
bool outside = compare(left -> left, right -> right); //(左)
// 表左右子树的内次节点是否相等
bool inside = compare(left -> right, right -> left); //(右)
bool result = outside && inside; //(中)
return result;
}
class Solution {
public:
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;
bool outside = compare(left -> left, right -> right);
bool inside = compare(left -> right, right -> left);
bool result = outside && inside;
return result;
}
bool isSymmetric(TreeNode* root) {
return compare(root -> left, root -> right);
}
};
(七)二叉树的最大深度
深度:二叉树中的任一节点到根节点的距离,根节点的深度为1**(前序遍历)**
往下遍历一个就+1,符合二叉树的遍历过程,前序遍历
高度:二叉树中的任一节点到叶子节点的距离,叶子节点的高度为1**(后序遍历)**
将叶子节点的高度返回给上面的父节点,父节点在这个基础上+1
根节点的最大高度即为最大深度
通过高度求深度(后序遍历)
int getheight(node){
if(node == NULL) return 0;
int leftheight = getheight(node -> left); 左
int rightheight = getheight(node -> right); 右
//中:处理逻辑,父节点在左右孩子结果的max基础上+1
int height = 1 + max(leftheight, rightheight);
// 根节点的深度,即为该二叉树的最大深度
return height;
}
return 1 + max(getheight(node -> left), getheight(node -> right))
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
queue<TreeNode*> que;
que.push(root);
int depth = 0;
while(!que.empty()){
int size = que.size();
depth ++;
while(size --){
TreeNode* node = que.front();
que.pop();
if(node -> left) que.push(node -> left);
if(node -> right) que.push(node -> right);
}
}
return depth;
}
};
(八)二叉树的最小深度
根节点到最近叶子节点的距离
求根节点的最小高度 → 二叉树的最小深度
使用后序遍历,注意什么是叶子节点!!
int getheight(TreeNode* node){
if(node == NULL) return 0;
int leftheight = getheight(node -> left); (左)
int rightheight = getheight(node -> right); (右)
// 不能直接返回leftheight和rightheight的最小值
// 特殊情况:左子树为空,右子树不为空,这时考虑的位右子树的最小高度,因为此时根节点不是叶子节点。而求最小深度计算的是到叶子节点的深度
// 左子树不为空,右子树为空同上
if(node -> left == NULL && node -> right != NULL) (中)
return 1 + rightheight;
if(node -> left != NULL && node -> right == NULL)
return 1 + leftheight;
// 正常情况,左右子树都非空
return 1 + min(leftheigth, rightheight);
}
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL) return 0;
int leftheight = minDepth(root -> left);
int rightheight = minDepth(root -> right);
if(root -> left == NULL && root -> right != NULL){
return rightheight + 1;
}else if(root -> left != NULL && root -> right == NULL){
return leftheight + 1;
}else{
return min(leftheight, rightheight) + 1;
}
}
};
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
queue<TreeNode*> que;
que.push(root);
int depth = 0;
while(!que.empty()){
int size = que.size();
depth ++;
while(size --){
TreeNode* node = que.front();
que.pop();
if(node -> left) que.push(node -> left);
if(node -> right) que.push(node -> right);
if(!node -> right && !node -> left) return depth;
}
}
return depth;
}
};
(九)完全二叉树的节点个数
(1)当做普通二叉树
递归遍历/层序遍历都可
int getNum(node){
if(node == NULL) return 0;
leftNum = getNum(Node -> left); // 左子树节点数量
rightNum = getNum(Node -> right); // 右子树节点数量
result = leftNum + rightNum + 1; // (中)左子树节点数+右子树节点数+根节点1个
return result;
}
(2)利用完全二叉树的特性
完全二叉树:除了底层节点,上面的节点都是满的,底层节点从左到右排列
除了最后一层,上面的为满二叉树,假设层数为n,则满二叉树的节点数为2^n - 1
如何判断一个子树为满二叉树:
如果从根节点一直向左遍历的深度=一直向右遍历的深度,则为满二叉树
(注意:因为这是一个完全二叉树,所以上面结论成立,最后一层中间不可能空)
如果该根节点对应的不是满二叉树,则继续向下遍历,判断下一个节点是否为满二叉树。叶子节点一定为二叉树,因此递归可以出来。
这样可以减少遍历的节点数
int getNum(node){
//终止条件 ①空节点
if(node == NULL) return 0;
left = node -> left;
right = node -> right;
leftdepth = 0; rightdepth = 0;
while(left){
left = left -> left;
leftdepth ++;
}
while(right){
right = right -> right;
rightdepth ++;
}
//终止条件 ②找到满二叉树,直接返回该子树的节点数
if(leftdepth == rightdepth){
// 注意这里的 2<<leftdepth,其实是2^(leftdepth+1) - 1
// 这里层数是从0开始计数,计算的时候从1开始算,自动加1
return (2 << leftdepth) -1;
}
// 单层递归逻辑
leftNum = getNum(node -> left); //左
rightNum = getNum(node -> right); //右
result = leftNum + rightNum + 1;
return result;
}
class Solution {
public:
int countNodes(TreeNode* root) {
// if(root == NULL) return 0;
// int leftNum = countNodes(root -> left);
// int rightNum = countNodes(root -> right);
// int result = leftNum + rightNum + 1;
// return result;
if(root == NULL) return 0;
TreeNode* left = root -> left;
TreeNode* right = root -> right;
int leftDepth = 0;
int rightDepth = 0;
while(left){
leftDepth ++;
left = left -> left;
}
while(right){
rightDepth ++;
right = right -> right;
}
if(leftDepth == rightDepth){
return (2 << leftDepth) - 1;
}
int leftNum = countNodes(root -> left);
int rightNum = countNodes(root -> right);
int result = leftNum + rightNum + 1;
return result;
}
};
(十)平衡二叉树
判断每个节点的左右子树的高度
采用后序遍历法
//求一个节点的高度,注意每一层递归调用多了一个异常判断-1
int getHeight(node){
if(node == NULL) return 0;
leftHeight = getHeight(node -> left); // 左
// 如果左子树非平衡二叉树,将结果直接返回到最上层递归,退出递归
if(leftHeight == -1) return -1;
rightHeight = getHeight(node -> right); // 右
// 如果右子树非平衡二叉树,将结果直接返回到最上层递归,退出递归
if(rightHeight == -1) return -1;
int result;
// 单层实现逻辑,如果对于根节点来说,左右子树高度差大于1,则返回-1
if(abs(rightHeight - leftHeight) > 1) result = -1; // 中
// 是平衡二叉树,则返回父节点的高度,为左右子树高度最大值加上本身的1
else result = 1 + max(rightHeight, leftHeight);
return result;
}
class Solution {
public:
int getHeight(TreeNode* root){
if(root == NULL) return 0;
int leftHeight = getHeight(root -> left);
if(leftHeight == -1) return -1;
int rightHeight = getHeight(root -> right);
if(rightHeight == -1) return -1;
if(abs(leftHeight - rightHeight) > 1) return -1;
else return max(leftHeight,rightHeight) + 1;
}
bool isBalanced(TreeNode* root) {
if(root == NULL) return true;
if(getHeight(root -> left) == -1 || getHeight(root -> right) == -1)
return false;
if(abs(getHeight(root -> left) - getHeight(root -> right)) > 1)
return false;
else return true;
}
};
(十一)二叉树的所有路径
前序遍历,让父节点不断指向孩子节点
递归法(回溯)
// path为一条路径,vector为结果集
void traversal(TreeNode* node, vector<int>& path, vector<string>& result){
//由于是到叶子节点就结束了,所以要先收集节点再判断终止
path.push_back(node -> val); //中
//终止条件,遍历到叶子节点
if(node -> left == NULL && node -> right == NULL){
result.push_back(path); // 注意先将数组转为string
return;
}
if(node -> left){
//有递归就有回溯,递归和回溯放一起
traversal(node -> left, path, result);
path.pop_back(); //回溯
}
if(node -> right){
traversal(node -> right, path, result);
path.pop_back();
}
}
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result){
path.push_back(cur->val);
if(cur -> left == NULL && cur -> right == NULL){
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();
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if(root == NULL) return result;
traversal(root, path, result);
return result;
}
};
补充:
将回溯隐藏,可以将参数改为 string path
,不加上引用&
,即本层递归中,path + “→”+ 该节点数值,但该层递归结束,上一层path的数值并不会受到任何影响。 (值传递和地址传递的区别)
class Solution {
public:
void traveral(TreeNode* cur, string path, vector<string>& result){
path += to_string(cur -> val);
if(cur -> left == NULL && cur -> right == NULL){
result.push_back(path);
return;
}
if(cur -> left) traveral(cur -> left, path + "->", result);
if(cur -> right) traveral(cur -> right, path + "->", result);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
string path;
if(root == NULL) return result;
traveral(root, path, result);
return result;
}
};
(十二)左叶子之和
(1)递归法
左叶子:叶子节点(左右孩子为空)
无法在遍历到叶子节点时判断该节点时左节点还是右节点,只有在遍历到父节点的时候判断其是否有左子树,并且左子树为叶子节点。
遍历顺序:后序遍历
收集左子树和右子树分别的左叶子节点之和,向父节点返回。
int tranversal(TreeNode node){
if(node == NULL) return 0;
if(node -> left == NULL && node -> right == NULL) return 0;
// 后序遍历
int leftNum = tranversal(node -> left); // 左
// 收集左叶子节点的值
if(node -> left != NULL && node -> left -> left == NULL && node -> left -> right == NULL)
leftNum = node -> left -> val;
int rightNum = tranversal(node -> right); // 右
int sum = leftNum + rightNum; // 中
return sum;
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL) return 0;
if(root -> left == NULL && root -> right == NULL) return 0;
int left = sumOfLeftLeaves(root -> left);
// 这里不能return,因为当前节点右节点的右子树还没有遍历
if(root -> left != NULL && root -> left -> left == NULL && root -> left -> right == NULL){
left += root -> left ->val;
}
int right = sumOfLeftLeaves(root -> right);
int sum = left + right;
return sum;
}
};
(2)迭代法
迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了。
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
stack<TreeNode*> st;
if(root == NULL) return 0;
st.push(root);
int result = 0;
while(!st.empty()){
TreeNode* node = st.top();
st.pop();
if(node -> left != NULL && node -> left -> left == NULL && node -> left -> right == NULL){
result += node -> left -> val;
}
if(node -> left) st.push(node -> left);
if(node -> right) st.push(node -> right);
}
return result;
}
};
(十三)找树左下角的值
(1)迭代法(较为简单)
只需要记录最后一行第一个节点的数值就可以了。
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
if(root != NULL) que.push(root);
int result = 0;
while(!que.empty()){
int size = que.size();
result = que.front()-> val;
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 result;
}
};
(2)递归法
深度最大的叶子节点,一定是最后一行
只要先遍历左节点就行,前序、中序、后序都可以
int maxDepth = INT_MIN; // 记录最大深度
int result; // 只要该节点的深度比最大深度大,都记录该节点的值
void traversal(root, depth){
if(root -> left == NULL && root -> right == NULL){
if(depth > maxDepth){
maxDepth = depth;
result = root -> val;
}
}
// 左
if(root -> left){
// 递归加上回溯,维护depth的值
depth ++;
traversal(root -> left, depth);
depth --;
// 精简:traversal(root -> left, depth + 1); 这里并没有改变depth的值,隐藏了回溯的过程
}
// 右
if(root -> right){
depth ++;
traversal(root -> right, depth);
depth --;
}
class Solution {
public:
int maxDepth = INT_MIN;
int result;
void traversal(TreeNode* root, int depth){
if(root -> left == NULL && root -> right == NULL){
if(depth > maxDepth){
maxDepth = depth;
result = root -> val;
}
return;
}
if(root -> left){
depth ++;
traversal(root -> left, depth);
depth --;
}
if(root -> right){
depth ++;
traversal(root -> right, depth);
depth --;
}
}
int findBottomLeftValue(TreeNode* root) {
traversal(root, 0);
return result;
}
};
(十四)路径总和
从根节点到叶子节点,路径上所有节点相加为目标和
前中后序遍历都可以
//传入目标sum,遇到一个节点将sum减去1,直到遇到叶子节点为止
bool traversal(TreeNode node, int count) {
if(node -> left == NULL && node -> right == NULL && count == 0)
return true;
if(node -> left == NULL && node -> right == NULL && count != 0)
return false;
if(node -> left){ //左
count -= node -> left -> val;
return traversal(node -> left, count); //左方向是否有符合题目要求的路径,将结果继续向上返回
count += node -> left -> val; //回溯,有减就有加
}
if(node -> right){ //右
count -= node -> right -> val;
return traversal(node -> right, count);
count += node -> right -> val;
}
return false;
}
//传入目标sum,遇到一个节点将sum减去1,直到遇到叶子节点为止
bool traversal(TreeNode node, int count) {
if(node -> left == NULL && node -> right == NULL && count == 0)
return true;
if(node -> left == NULL && node -> right == NULL && count != 0)
return false;
if(node -> left){ //左
return traversal(node -> left, count - node -> left -> val);//只是参数改变了,count的值没有变,因此隐藏了回溯的过程
}
if(node -> right){ //右
return traversal(node -> right, count - node -> right -> val);
}
return false;
}
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (!root) return false;
if(!root->left && !root->right && sum != root->val){
return false;
}
if (!root->left && !root->right && sum == root->val) {
return true;
}
if(root -> left){
sum -= root -> val;
//是false的时候不能返回,因为这时候可能右子树还没遍历完,而只要有一个满足条件,即结果为true,可以直接返回
//这里如果得到一个可能的结果,必须将true层层传递上去,否则结果被后面的false盖住,得不到正确结果
if(hasPathSum(root -> left, sum)) return true;
sum += root -> val;
}
if(root -> right){
sum -= root -> val;
if(hasPathSum(root -> right, sum)) return true;
sum += root -> val;
}
return false;
}
};
(十五)从中序与后序遍历序列构造二叉树
以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
6步:
step1:如果后序数组大小为0,则返回空节点。
step2:如果后序数组长度不为0,则取后序数组最后一个元素作根节点元素。
step3:找到后序数组最后一个元素在中序数组的位置,作为切割点。
step4切割中序数组,切成中序左数组和中序右数组。
step5:根据中序数组的切割情况,切割后序数组为后序左数组和后序右数组。
step6:递归处理左区间和右区间。
TreeNode* traversal(inorder,preorder){
if(preorder.size == 0) return null;
rootvalue = preorder[preorder.size - 1]; //中间节点
root = new TreeNode(rootvalue);
if(preorder.size == 1) return root; //只有一个节点
int index = 0; //切割位置(可以用来切中序和后序)
for(index = 0; 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> leftInorder(inorder.begin, inorder.begin + index);
vector<int> rightInorder(inorder.begin + index, inorder.end - 1);
//递归处理左区间和右区间
root -> left = traversal(左中序,左后序);
root -> right = traversal(右后序,右中序);
return root;
}
lass Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0) return NULL;
int rootvalue = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(rootvalue);
if(inorder.size() == 1) return root;
int index;
for(index = 0; 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;
}
};
(补充)中序和前序可以确定唯一一棵二叉树
前序最前面一个元素确定根节点,其他同上
注:前序和后序不能确定一棵二叉树,因为这两种顺序左右子树是连在一起的,无法分割。
总结:必须要有中序才能确定
(十六)最大二叉树
前序遍历,先找到最大的元素,构造根节点,再构造左子树和右子树
(构造二叉树类的题目,用前序遍历,必须要先把根节点构造出来)
TreeNode* contruct(nums){
if(num.size == 1) {//终止条件,到叶子节点,这里默认数组默认有1个元素(题目要求),这里影响了下面左右递归前的条件判断。
return new TreeNode(nums[0]);
}
int maxValue = 0;//数组里面的最大值,用来构造根节点(数组里面所有数为正数)
int index = 0;//数组最大值对应的下表,用来分割数组
for(int i = 0; i < nums.size(); i++){
if(nums[i] > maxValue){
maxValue = nums[i];
index = i;
}
}
node = new TreeNode(maxValue);//中
//分割左右子树,递归。但是要保证左右区间的大小要大于1,因为这里没有判断数组大小等于0的情况
if(index > 0){
newVec(0, index); //左闭右开
node -> left = contruct(newVec);
}
if(index < nums.size() - 1){
newVec(index + 1, nums.size());
node -> right = construct(newVec);//左闭右开
}
return root;
}
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.size() == 1){
return new TreeNode(nums[0]);
}
int maxValue = 0;
int index = 0;
for(int i = 0; i < nums.size(); i++){
if(nums[i] > maxValue){
maxValue = nums[i];
index = i;
}
}
TreeNode* root = new TreeNode(maxValue);
if(index > 0){
//注意这里必须是从nums.begin()开始,而不是从0开始
vector<int> leftNums(nums.begin(), nums.begin() + index);
root -> left = constructMaximumBinaryTree(leftNums);
}
if(index < nums.size() - 1){
vector<int> rightNums(nums.begin() + index + 1, nums.end());
root -> right = constructMaximumBinaryTree(rightNums);
}
return root;
}
};
优化:不要每次都构造新的数组,每次传入开始和结束的下表即可。
(十七)合并二叉树
同时遍历两个二叉树(传入两个树的节点,同时操作)
(1)递归法
前中后序遍历都可以,可以另外构造一棵树,也可以直接修改t1的结构和值
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == NULL) return root2;
if(root2 == NULL) return root1;
TreeNode* t1 = new TreeNode();
t1 -> val = root1 -> val + root2 -> val;
t1 -> left = mergeTrees(root1 -> left, root2 -> left);
t1 -> right = mergeTrees(root1 -> right, root2 -> right);
return t1;
}
};
(2)迭代法
模拟层序遍历,这里就不需要统计到了第几层了,只要每次取出两个节点,对应配对的两个子树的节点处理了就行。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == NULL) return root2;
if(root2 == NULL) return root1;
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
while(!que.empty()){
TreeNode* node1 = que.front(); que.pop();
TreeNode* node2 = que.front(); que.pop();
node1->val = node1->val + node2->val;
if(node1->left != NULL && node2->left != NULL){
que.push(node1->left);
que.push(node2->left);
}
if(node1->right != NULL && node2->right != NULL){
que.push(node1->right);
que.push(node2 -> right);
}
if(node1->left == NULL && node2->left != NULL){
node1 -> left = node2 -> left;
}
if(node1->right == NULL && node2->right != NULL){
node1 -> right = node2 -> right;
}
}
return root1;
}
};
(十八)二叉搜索树中的搜索
二叉搜索树是一个有序数:
若其左子树不空,则左子树上所有节点的值均小于它的根节点的值。
若其右子树不空,则右子树上所有节点的值均大于它的根节点的值。
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
//这里要把root == NULL 写在前面,确保后面的root -> val有意义
if(root == NULL || root -> val == val ) return root;
TreeNode* tree = NULL;
if(root -> val > val) tree = searchBST(root -> left, val);
if(root -> val < val) tree = searchBST(root -> right, val);
return tree;
}
};
(十九)验证二叉搜索树
判断一个二叉树是否为有效二叉树
(1)整理转化为数组进行判断
关键:中序遍历下,输出的二叉树节点的数值是有序序列
转化为:判断中序遍历二叉树生成的数组是否是递增的
注意:数组里面也不能有相同的元素
class Solution {
public:
//注意这个不能再递归函数里面定义,因为每次递归会刷新他
vector<int> result;
bool isValidBST(TreeNode* root) {
if(root == NULL) return true;
isValidBST(root -> left);
result.push_back(root -> val);
isValidBST(root -> right);
for(int i = 1; i < result.size(); i ++){
if(result[i] <= result[i - 1]){
return false;
}
}
return true;
}
};
(2)一边遍历一边判断
误区:根节点的左子树中所有节点都必须小于根节点,右子树中所有节点都必须大学根节点
因此不能只判断一个左孩子和一个右孩子
long long maxVal = Long_MIN;//用这个全局变量确定节点是有序的,因为这里节点的值可能出现int的最小值,为了比这个值更小,只能用Long long型变量
bool isvalid(TreeNode* root){
if(root == NULL) return true;
bool left = isvalid(root -> left); //左
//在中序遍历的过程中,节点值一直是增大的,因此这个maxValue其实是记录的当前节点的前一个遍历到的节点的值
if(root -> value > maxValue){ //中
maxValue = root -> val;
}else{
return false;
}
bool right = isValid(root -> right);//右
return left && right;
}
可以不定义这个maxValue临时变量,而用双指针法,直接进行前一个节点和后一个节点比较
TreeNode* pre = NULL;
bool isvalid(TreeNode* root){
if(root == NULL) return true;
bool left = isvalid(root -> left); //左
//pre!=NULL,跳过第一个节点
if(pre != NULL && pre -> val >= root -> val){ //中
return false;
}
pre = root;
bool right = isValid(root -> right);//右
return left && right;
}
(二十)二叉搜索树的最小绝对差
在二叉树搜索树上求最值、差值 → 在有序数组上求最值、差值(二叉搜索树的有序性)
(1)转为有序数组
class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root){
if(root == NULL) return;
traversal(root -> left);
vec.push_back(root -> val);
traversal(root -> right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
if(vec.size() < 2) return true;
int result = INT_MAX;
for(int i = 1; i < vec.size(); i++){
result = min(result, vec[i] - vec[i - 1]);
}
return result;
}
};
(2)一边遍历一边比较
class Solution {
private:
TreeNode* pre = NULL;
int result = INT_MAX;
void traversal(TreeNode* root){
if(root == NULL) return;
traversal(root -> left);
if(pre != NULL){
result = min(result, root -> val - pre -> val);
}
pre = root;
traversal(root -> right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
(二十一)二叉搜索树中的众数
有相同值的二叉搜索树
找出所有众数
(1)当做非二叉搜索树来做
将二叉树遍历,用Map统计每种元素出现的频率,再对统计出来的频率排序
class Solution {
private:
void searchBST(TreeNode* root, unordered_map<int, int>& map){
if(root == NULL) return;
map[root -> val] ++;
searchBST(root -> left, map);
searchBST(root -> right, map);
}
bool static cmp(const pair<int,int>& a, const pair<int, int>& b){
return a.second > b.second; //从大到小排序
}
public:
vector<int> findMode(TreeNode* root) {
unordered_map<int, int> map;
vector<int> result;
searchBST(root, map);
vector<pair<int, int>> vec(map.begin(),map.end());
sort(vec.begin(), vec.end(), cmp);
result.push_back(vec[0].first);
for(int i = 1; i < vec.size(); i++){
if(vec[i].second == vec[0].second){
result.push_back(vec[i].first);
}
}
return result;
}
};
(2)当做二叉搜索树来做
中序遍历,相同的元素都是临近的,用count来计数重复次数,用maxCount来保存重复次数最多的。
class Solution {
private:
vector<int> vec;
TreeNode* pre = NULL;
int count;
int maxCount = INT_MIN;
void traversal(TreeNode* root){
if(root == NULL) return;
traversal(root -> left); //左
if(pre == NULL){ //中
count = 1;
}else{
if(root -> val == pre -> val)
count ++;
else
count = 1;
}
pre = root;
if(count == maxCount){//之前找到的不是出现频率最高的众数
vec.push_back(root -> val);
}
if(count > maxCount){
maxCount = count;
vec.clear(); //这里注意要清空之前数组中的内容
vec.push_back(root -> val);
}
traversal(root -> right); //右
}
public:
vector<int> findMode(TreeNode* root) {
vec.clear();
traversal(root);
return vec;
}
};
(二十二)二叉树的最近公共祖先
最近公共祖先节点可以为节点本身
回溯:从低往上遍历
后序遍历:左-右-中
情况1:左右子树中是否出现了p或q,中间节点负责向上返回
情况2:p和q某个出现在中间节点(在处理情况1的同时也处理情况2)
TreeNode* traversal(root, p, q){
if(root == NULL) return NULL;
if(root == p || root == q) return root;
TreeNode* left = traversal(root -> left, p , q);//左
TreeNode* right = traversal(root -> right, p, q);//右
if(left != NULL && right != NULL) return root; //找到最近公共祖先
if(left == NULL && right != NULL) return right; //结果往上逐层返回
if(left != NULL && right == NULL) return left;
else return NULL;//左右都不包含目标节点
}
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL) return NULL;
if(root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root -> left, p, q);
TreeNode* right = lowestCommonAncestor(root -> right, p, q);
if(left && right) return root;
if(left && !right) return left;
if(!left && right) return right;
return NULL;
}
};
(二十三)二叉搜索树的最近公共祖先
只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先,可以证明这个也是最近公共祖先
(1)递归法
(相当于变形的前序遍历,只是中是条件判断而已)
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL) return NULL;
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;
}
}
};
(2)迭代法
这里由于二叉搜索树的有序性,不需要回溯,路径有顺序规定好
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root){
if(root -> val < p -> val && root -> val < q -> val){
root = root -> right;
}else if(root -> val > p -> val && root -> val > q -> val){
root = root -> left;
}else{
return root;
}
}
return NULL;
}
};
(二十四)二叉搜索树中的插入操作
找到空节点插入元素,不需要调整树的结构
(1)递归法
有返回值,可以利用返回值完成新加入的节点与其父节点的赋值操作。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
TreeNode* newNode = new TreeNode(val);
return newNode;
}
if(root -> val < val){
root -> right = insertIntoBST(root -> right, val);
}
if(root -> val > val){
root -> left = insertIntoBST(root -> left, val);
}
return root;
}
};
(2)迭代法
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(root == NULL){
TreeNode* node = new TreeNode(val);
return node;
}
TreeNode* pre = root;// 记录最后一个找到的节点,否则无法将新节点连接到原二叉树上
TreeNode* firstRoot = root;//记录头结点,最后返回更新后的整个树
while(root){
if(root -> val < val){
pre = root; //pre记录上一个节点
root = root -> right;
}else if(root -> val > val){ //注意这里必须写else,递归法可能没影响,但是迭代法影响
pre = root;
root = root -> left;
}
}
TreeNode* node = new TreeNode(val);
if(pre -> val < val) pre -> right = node;
if(pre -> val > val) pre -> left = node;
return firstRoot;
}
};
(二十五)删除二叉搜索树中的节点
删除节点→改变二叉树的结构
要符合二叉搜索树的结果
情况一:没找到要删的节点
情况二:找到,要删除的为叶子节点(最简单)
情况三:需要删除的节点左不空,右为空,让父节点直接指向左孩子
情况四:需要删除的节点左为空,右不空,让父节点直接指向右孩子
情况五:需要删除的节点左右孩子都不为空,右子树替补,左子树挂到右子树的最左叶子节点的左边(最复杂)
注意在C++中要手动释放内存
TreeNode* delete(root, key){//返回删掉目标节点后新的二叉树的根节点
//不需要遍历整个二叉树,找到要删除的节点,执行删除逻辑,就退出遍历
if(root == NULL) return NULL;// 没有找到删除节点
if(root -> val == key){
if(root -> left == NULL && root -> right == NULL){//情况二
return NULL;
}else if(root -> left != NULL && root -> right == NULL){//情况三
return root -> left;
}else if(root -> left == NULL && root -> right != NULL){//情况四
return root -> right;
}else{ //情况五
cur = root -> right;
while(cur -> left){
cur = cur -> left; //找到右子树的最左边节点
}
cur -> left = root -> left; //要删的节点的左子树挂到cur上
return root -> right; //用右子树替掉当前节点
}
}
if(key < root -> val)//根据二叉搜索树的特性,决定搜索左子树还是右子树
root -> left = delete(root -> left, key);
if(key > root -> val)
root -> right = delete(root -> right, key);
return root;
}
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == NULL) return NULL;
if(root -> val == key){
if(root -> left == NULL && root -> right == NULL){
delete root;
return NULL;
}else if(root -> left == NULL && root -> right != NULL){
return root -> right;
}else if(root -> left != NULL && root -> right == NULL){
return root -> left;
}else{
TreeNode* cur = root -> right;
while(cur -> left){
cur = cur -> left;
}
cur -> left = root -> left;
return root -> right;
}
}
if(root -> val > key){
root -> left = deleteNode(root -> left, key);
}
if(root -> val < key){
root -> right = deleteNode(root -> right, key);
}
return root;
}
};
(二十六)修剪二叉搜索树
给定范围修剪二叉搜索树
不是当前节点的val不在范围内就return NULL!!! 因为可能他的 左/右子树里面有节点在范围内
当前val小于左边界,右子树中节点可能符合范围
当前val大于右边界,左子树中节点可能符合范围
TreeNode* traversal(root, low, high){
if(root == NULL) return NULL;
if(root -> val < low) {
right = traversal(root -> right, low, high); //因为右子树中可能也有不符合范围的,因此要在这个规则下继续遍历右子树,返回修剪后的右子树根节点,不能直接返回右子树
return right;
}
if(root -> val > high){
left = traversal(root -> left, low, high);
return left;
}
//这里是左右子树都要修剪
root -> left = traversal(root -> left, low, high);
root -> right = traversal(root -> right, low, high);
return root;
}
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == NULL) return NULL;
if(root -> val < low){
return trimBST(root -> right, low, high);
}
if(root -> val > high){
return trimBST(root -> left, low, high);
}
root -> left = trimBST(root -> left, low, high);
root -> right = trimBST(root -> right, low, high);
return root;
}
};
(二十七)将有序数组转换为二叉搜索树
构造平衡二叉搜索树
每次都选取数组中间的,构造根节点,再递归构造左右子树
数组长度为奇数:取中间的数
数组长度为偶数:取靠右侧/靠左侧作为根节点(都可以)
TreeNode* traversal(vector<int>& nums, left, right){
//设置为左闭右闭区间,当Left==right时,还是一个合法区间
if(left > right) return NULL;
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 {
private:
TreeNode* traversal(vector<int>& nums, int left, int right){
if(left > right) return NULL;
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;
}
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
TreeNode* root = traversal(nums, 0, nums.size() - 1);
return root;
}
};
(二十八)把二叉搜索树转换为累加树
每个节点node的新值等于原树中大于或等于node.val的值之和
相加的顺序:从最大的节点开始遍历相加,右-中-左(反中序遍历),从大到小遍历
双指针操作,cur指向当前操作的节点,pre记录上一个节点,就是需要累加到本节点的值
(本题是迭代法的一个模板题)
int pre = 0; //代表前一个节点的值,一开始为0,如果定义为TreeNode,可能会有空指针异常
void traversal(TreeNode* cur){//传入二叉搜索树的根节点
if(cur == NULL) return;
traversal(cur -> right); //右
cur -> val += pre; //中
pre = cur -> val; //pre移动
traversal(cur -> left);//左
}
class Solution {
private:
int pre = 0;
void traversal(TreeNode* cur){
if(cur == NULL) return;
traversal(cur -> right);
cur -> val += pre;
pre = cur -> val;
traversal(cur -> left);
}
public:
TreeNode* convertBST(TreeNode* root) {
traversal(root);
return root;
}
};