二叉树

37 阅读28分钟

1.二叉树基础

计算:高度为h的满二叉树有2h12^{h}-1个节点,第h层有2h12^{h-1}个节点,例如:高度为3的满二叉树,总共有231=72^3-1=7节点,第三层有22=42^2=4个节点

image.png

非递归遍历

2.前序:

  1. 根节点先入栈,访问根节点并出栈。
  2. 如果右孩子不为空,则右孩子入栈,如果左孩子不为空,再把左孩子入栈。这样做可以保持节点的左孩子先被访问。
  3. 处理刚入栈的元素,循环直到栈空。
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == nullptr) return {};
        
        stack<TreeNode*> stk;
        vector<int> result;

        stk.push(root);

        while(!stk.empty()){
            TreeNode* cur = stk.top();
            stk.pop();
            result.push_back(cur->val);

            if(cur->right != nullptr){
                stk.push(cur->right);
            }
            if(cur->left != nullptr){
                stk.push(cur->left);
            }
        }

        return result;
    }
};

3.中序:

  1. 出门就往左走到头,每个元素都入栈
  2. 访问栈顶出栈,如果有右孩子,那么右孩子入栈
  3. 循环直到栈空
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if(root == nullptr) return {};
        stack<TreeNode*> stk;
        vector<int> result;
        TreeNode* cur = root;
        while(cur != nullptr || !stk.empty()){
            if(cur != nullptr){
                stk.push(cur);
                cur = cur->left;
            }
            else{
                cur = stk.top();
                stk.pop();
                result.push_back(cur->val);
                cur = cur->right;
            }
        }
        return result;
    }
};

4.后序:

后续的把先序的换一下顺序,最后再调换就行

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if(root == nullptr) return {};
        
        stack<TreeNode*> stk;
        vector<int> result;

        stk.push(root);

        while(!stk.empty()){
            TreeNode* cur = stk.top();
            stk.pop();
            result.push_back(cur->val);
            //出栈顺序是中右左
            if(cur->left != nullptr){
                stk.push(cur->left);
            }

            if(cur->right != nullptr){
                stk.push(cur->right);
            }

        }
        reverse(result.begin(), result.end());
        return result;
    }
};

后序题目

翻转二叉树

题目链接:226. 翻转二叉树
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

思路 递归进行处理,每次先将当前节点子树中的节点进行递归翻转。然后将当前节点的left指向当前的右子树,right指向当前的左子树,做翻转操作。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        doInvert(root);
        return root;
    }

    void doInvert(TreeNode* node){
        if(node == nullptr) return;

        doInvert(node->left);
        doInvert(node->right);

        TreeNode* leftTree = node->left;
        TreeNode* rightTree = node->right;
        node->left = rightTree;
        node->right = leftTree;
    }
};

5.层序:

  1. 一个队列实现,当访问元素并出栈时候,有孩子就把孩子装进去
  2. 循环直到队列为空
  3. 可以把每一层标记层级
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;
    }
};

层序相关题目:

102. 二叉树的层序遍历

思路:标准的层序遍历,利用上述的版子就可以解答

107. 二叉树的层序遍历 II

思路:先做层序遍历,然后将结果反转

199. 二叉树的右视图

思路:进行层次遍历,每次将每层的最后一个节点的值添加到结果中

637. 二叉树的层平均值

思路:进行层次遍历,然后对每层的结果做平均,注意每个节点的数值是否越过int的边界

429. N 叉树的层序遍历

思路:和二叉树的层序遍历类似,采用队列作为容器来实现 515. 在每个树行中找最大值

思路:用层序方式进行遍历,然后找出每层中的最大值,添加入结果

116. 填充每个节点的下一个右侧节点指针

思路:进行层序遍历,然后在遍历的过程中不断地将每层的前一个节点指向后一个节点 117. 填充每个节点的下一个右侧节点指针 II

思路:这题和上一题一模一样,进行层序遍历,然后把前一个节点指向后一个节点

104. 二叉树的最大深度

思路:可以采用层序遍历来解决,每一层记一次数,总数就是二叉树的最大深度 111. 二叉树的最小深度

思路:进行层次遍历,如果在遍历时,发现当前有一个节点既没有左子树又没有右子树,则当前节点的深度为二叉树的最小深度

6.对称二叉树

题目链接:101. 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

思路

首先,如果root为空或者root没有左右子树,则说明对称。

如果root两棵树不全为空,或者都不为空,但是值不相等,则说明不对称。

否则进行递归处理,判断当前对应的两棵子树是否对称,判断的根据还是根据两棵子树是否为空、两棵子树的对称位置子树是否一致等。具体参见代码。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root == nullptr) return true;

        if(root->left == nullptr && root->right == nullptr){
            return true;
        }
        else if(root->left != nullptr && root->right != nullptr && root->left->val == root->right->val){
            return doJudge(root->left, root->right);
        }
        else{
            return false;
        }
    }

    bool doJudge(TreeNode* leftPart, TreeNode* rightPart){
        if(leftPart == nullptr && rightPart == nullptr){
            return true;
        }
        else if(leftPart != nullptr && rightPart != nullptr && leftPart->val == rightPart->val){
            bool result = doJudge(leftPart->left, rightPart->right) && doJudge(leftPart->right, rightPart->left);
            return result;
        }
        else{
            return false;
        }
    }
};

7.二叉树的最大深度

题目链接:104. 二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

思路

可以采用递归和迭代两种方法实现。

递归:求得当前节点左右孩子的深度depth1和depth2,取二者最大值+1,便为当前节点到叶子节点的最大长度。从根节点递归求解这一过程,便可得出二叉树的最大深度。

迭代:用层序遍历来解决,使用depth变量记录当前节点深度,每一层遍历将depth+1,一直到遍历结束,便可得到最大深度值。

8.最小深度

思路

与上一题类似,也可采用递归或迭代法来解决。

递归:使用一个min_depth变量记录二叉树的全局最小深度,然后递归搜寻二叉树,记录每个叶子节点的深度,将最小的深度赋予min_depth。

迭代:采用层序遍历,如果在遍历时,第一次发现当前有一个节点既没有左子树又没有右子树,则当前节点的深度为二叉树的最小深度。

// 递归法
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == nullptr) return 0;
        int min_depth = numeric_limits<int>::max();
        dfs(root, 1, min_depth);
        return min_depth;
    }

    void dfs(TreeNode* node, int cur_depth, int& min_depth){
        if(node == nullptr) return;
        if(node->left == nullptr && node->right == nullptr){
            min_depth = min(min_depth, cur_depth);
        }
        if(node->left) dfs(node->left, cur_depth + 1, min_depth);
        if(node->right) dfs(node->right, cur_depth + 1, min_depth);
    }
};
// 迭代法
class Solution {
public:
    int minDepth(TreeNode* root) {
        queue<TreeNode*> queue;
        int minDepth = 0;//维护最小值
        if(root != nullptr) queue.push(root);
        bool foundLeaf = false;
        while(!queue.empty()){
            int qSize = queue.size();
            minDepth += 1;
            for(int i = 0;i<qSize;i++){
                TreeNode* cur = queue.front();
                queue.pop();
                bool isLeaf = !cur->left && !cur->right;
                if(isLeaf){//发现叶节点
                    foundLeaf = true;
                    break;
                }

                if(cur->left) queue.push(cur->left);
                if(cur->right) queue.push(cur->right);
            }
            if(foundLeaf) break;
        }
        return minDepth;
    }
};

9.平衡二叉树

查找效率高,但调整麻烦

Code

#include "stdio.h"
#include "stdlib.h"   
#include "io.h"  
#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;	/* Status是函数的类型,其值是函数结果状态代码,如OK等 */


/* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode	/* 结点结构 */
{
	int data;	/* 结点数据 */
	int bf; /*  结点的平衡因子 */
	struct BiTNode* lchild, * rchild;	/* 左右孩子指针 */
} BiTNode, * BiTree;


/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree* P)
{
	BiTree L;
	L = (*P)->lchild; /*  L指向P的左子树根结点 */
	(*P)->lchild = L->rchild; /*  L的右子树挂接为P的左子树 */
	L->rchild = (*P);
	*P = L; /*  P指向新的根结点 */
}

/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0  */
void L_Rotate(BiTree* P)
{
	BiTree R;
	R = (*P)->rchild; /*  R指向P的右子树根结点 */
	(*P)->rchild = R->lchild; /* R的左子树挂接为P的右子树 */
	R->lchild = (*P);
	*P = R; /*  P指向新的根结点 */
}

#define LH +1 /*  左高 */ 
#define EH 0  /*  等高 */ 
#define RH -1 /*  右高 */ 

/*  对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/*  本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree* T)
{
	BiTree L, Lr;
	L = (*T)->lchild; /*  L指向T的左子树根结点 */
	switch (L->bf)
	{ /*  检查T的左子树的平衡度,并作相应平衡处理 */
	case LH: /*  新结点插入在T的左孩子的左子树上,要作单右旋处理 */
		(*T)->bf = L->bf = EH;
		R_Rotate(T);
		break;
	case RH: /*  新结点插入在T的左孩子的右子树上,要作双旋处理 */
		Lr = L->rchild; /*  Lr指向T的左孩子的右子树根 */
		switch (Lr->bf)
		{ /*  修改T及其左孩子的平衡因子 */
		case LH: (*T)->bf = RH;
			L->bf = EH;
			break;
		case EH: (*T)->bf = L->bf = EH;
			break;
		case RH: (*T)->bf = EH;
			L->bf = LH;
			break;
		}
		Lr->bf = EH;
		L_Rotate(&(*T)->lchild); /*  对T的左子树作左旋平衡处理 */
		R_Rotate(T); /*  对T作右旋平衡处理 */
	}
}

/*  对以指针T所指结点为根的二叉树作右平衡旋转处理, */
/*  本算法结束时,指针T指向新的根结点 */
void RightBalance(BiTree* T)
{
	BiTree R, Rl;
	R = (*T)->rchild; /*  R指向T的右子树根结点 */
	switch (R->bf)
	{ /*  检查T的右子树的平衡度,并作相应平衡处理 */
	case RH: /*  新结点插入在T的右孩子的右子树上,要作单左旋处理 */
		(*T)->bf = R->bf = EH;
		L_Rotate(T);
		break;
	case LH: /*  新结点插入在T的右孩子的左子树上,要作双旋处理 */
		Rl = R->lchild; /*  Rl指向T的右孩子的左子树根 */
		switch (Rl->bf)
		{ /*  修改T及其右孩子的平衡因子 */
		case RH: (*T)->bf = LH;
			R->bf = EH;
			break;
		case EH: (*T)->bf = R->bf = EH;
			break;
		case LH: (*T)->bf = EH;
			R->bf = RH;
			break;
		}
		Rl->bf = EH;
		R_Rotate(&(*T)->rchild); /*  对T的右子树作右旋平衡处理 */
		L_Rotate(T); /*  对T作左旋平衡处理 */
	}
}

/*  若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
/*  数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
/*  失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree* T, int e, Status* taller)
{
	if (!*T)
	{ /*  插入新结点,树“长高”,置taller为TRUE */
		*T = (BiTree)malloc(sizeof(BiTNode));
		(*T)->data = e; (*T)->lchild = (*T)->rchild = NULL; (*T)->bf = EH;
		*taller = TRUE;
	}
	else
	{
		if (e == (*T)->data)
		{ /*  树中已存在和e有相同关键字的结点则不再插入 */
			*taller = FALSE; return FALSE;
		}
		if (e < (*T)->data)
		{ /*  应继续在T的左子树中进行搜索 */
			if (!InsertAVL(&(*T)->lchild, e, taller)) /*  未插入 */
				return FALSE;
			if (*taller) /*   已插入到T的左子树中且左子树“长高” */
				switch ((*T)->bf) /*  检查T的平衡度 */
				{
				case LH: /*  原本左子树比右子树高,需要作左平衡处理 */
					LeftBalance(T);	*taller = FALSE; break;
				case EH: /*  原本左、右子树等高,现因左子树增高而使树增高 */
					(*T)->bf = LH; *taller = TRUE; break;
				case RH: /*  原本右子树比左子树高,现左、右子树等高 */
					(*T)->bf = EH; *taller = FALSE; break;
				}
		}
		else
		{ /*  应继续在T的右子树中进行搜索 */
			if (!InsertAVL(&(*T)->rchild, e, taller)) /*  未插入 */
				return FALSE;
			if (*taller) /*  已插入到T的右子树且右子树“长高” */
				switch ((*T)->bf) /*  检查T的平衡度 */
				{
				case LH: /*  原本左子树比右子树高,现左、右子树等高 */
					(*T)->bf = EH; *taller = FALSE;	break;
				case EH: /*  原本左、右子树等高,现因右子树增高而使树增高  */
					(*T)->bf = RH; *taller = TRUE; break;
				case RH: /*  原本右子树比左子树高,需要作右平衡处理 */
					RightBalance(T); *taller = FALSE; break;
				}
		}
	}
	return TRUE;
}

int main(void)
{
	int i;
	int a[10] = { 3,2,1,4,5,6,7,10,9,8 };
	BiTree T = NULL;
	Status taller;
	for (i = 0; i < 10; i++)
	{
		InsertAVL(&T, a[i], &taller);
	}
	printf("本样例建议断点跟踪查看平衡二叉树结构");
	return 0;
}

10.二叉树的所有路径

题目链接:257. 二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

思路

用递归的方式进行遍历,可以采用前序遍历。记录下从根节点到叶节点沿途的路径,返回结果。具体见代码。

这里采用前序遍历来解决,那么也可以用迭代的前序遍历来做。

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        if(root == nullptr) return {};
        string path;
        vector<string> results;

        dfs(root, path, results);
        return results;
    }

    void dfs(TreeNode* node, string path, vector<string>& results){
        if(node->left == nullptr && node->right == nullptr){
            path += "->";
            path += to_string(node->val);
            results.push_back(path.substr(2, path.size() - 2));
            return;
        }

        path += "->";
        path += to_string(node->val);
        if(node->left != nullptr) dfs(node->left, path, results);
        if(node->right != nullptr) dfs(node->right, path, results);
    }
};

11.左叶子之和

题目链接:404.左叶子之和

image.png

思路

回溯算法,从根节点开始往下深度遍历,如果是左叶节点就加上来

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if(root == nullptr) return 0;
        int sum = 0;
        dfs(root, 2, sum);
        return sum;
    }

    void dfs(TreeNode* node, int dir, int& sum){
        // 定义dir = 0为左节点,dir = 1为右节点, dir = 2表示根节点
        if(dir == 0 && node->left == nullptr && node->right == nullptr){
            sum += node->val;
            return;
        }

        if(node->left) dfs(node->left, 0, sum);
        if(node->right) dfs(node->right, 1, sum);
    }
};

12.找树左下角的值

题目链接:513.找树左下角的值

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

思路 采用层序遍历的方式实现,记录下遍历每层时的第一个节点的值left_val,随着一层层地遍历,left_val的值也在不断更新,最终获得的left_val的值便是树的左下角的值。

看了卡哥的题解之后,也可以采用递归的方式来处理。要找到树左下角的值,即寻找树的最后一行的最左边的值。当采用前序遍历(中序或后序亦可)时,遍历到的每一行的第一个节点就是每一行最左边的节点。因此可以采用递归的前序遍历来解,在每次遍历时记录当前深度,如果当前深度比之前记录的最大深度要大,那么说明此时来到了新的一行,当前访问的节点为新的一行的最左边的节点。

// 层序遍历求解
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que;
        int left_val = -1;
        que.push(root);
        while(!que.empty()){
            int q_size = que.size();
            for(int i = 0;i<q_size;i++){
                TreeNode* cur = que.front();
                que.pop();
                if(i == 0) left_val = cur->val;
                if(cur->left) que.push(cur->left);
                if(cur->right) que.push(cur->right);
            }
        }

        return left_val;
    }
};
// 前序递归遍历求解
class Solution {
public:
    int maxDepth;
    int leftBottom;
    int findBottomLeftValue(TreeNode* root) {
        maxDepth = numeric_limits<int>::min();
        dfs(root, 1);
        return leftBottom;
    }

    void dfs(TreeNode* node, int depth){
        if(depth > maxDepth){
            maxDepth = depth;
            leftBottom = node->val;
        }

        if(node->left) dfs(node->left, depth + 1);
        if(node->right) dfs(node->right, depth + 1);
    }
};

13.路径总和

题目链接:112.路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

思路

用递归的方式来处理,递归函数携带当前已经累计的和,以及目标和,如果当前节点为叶子节点,且累计和与当前节点的值相加等于目标和,则说明找到了一个这样的节点,返回true。否则返回false。

也可以用迭代的方式来处理,用栈来模拟前序遍历,不过这时栈不仅要记录该节点指针,还要记录从头节点到该节点的路径数值总和。

// 递归实现
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        return traversal(root, 0, targetSum);
    }

    bool traversal(TreeNode* node, int curSum, const int& targetSum){
        if(node == nullptr) return false;
        if(node->left == nullptr && node->right == nullptr){
            if(curSum + node->val == targetSum){
                return true;
            }
        }
        
        bool leftSuccess = traversal(node->left, curSum + node->val, targetSum);
        bool rightSuccess = traversal(node->right, curSum + node->val, targetSum);
        return leftSuccess || rightSuccess;
    }
};

// 迭代实现
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        stack<pair<TreeNode*, int>> stk;
        if(root == nullptr) return false;
        stk.push({root, root->val});

        while(!stk.empty()){
            auto cur = stk.top();
            stk.pop();
            if(!cur.first->left && !cur.first->right && cur.second == targetSum){
                return true;
            }
            if(cur.first->right){
                stk.push({cur.first->right, cur.second + cur.first->right->val});
            }
            if(cur.first->left){
                stk.push({cur.first->left, cur.second + cur.first->left->val});
            }
        }
        return false;
    }
};

路径和2

image.png

14.构造二叉树

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        return traversal(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1);
    }

    TreeNode* traversal(const vector<int>& preorder, const vector<int>& inorder, int l1, int r1, int l2, int r2){
        if(l1 > r1) return nullptr;
        int headVal = preorder[l1];
        TreeNode* head = new TreeNode(headVal);

        int i;
        for(i = l2;i<=r2;i++){
            if(inorder[i] == headVal) break;
        }

        head->left = traversal(preorder, inorder, l1 + 1, l1 + i - l2, l2, i - 1);
        head->right = traversal(preorder, inorder, l1 + i - l2 + 1, r1, i + 1, r2);
        return head;
    }
};

15.最大二叉树

题目链接:654.最大二叉树

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

image.png

创建一个根节点,其值为 nums 中的最大值。 递归地在最大值 左边 的 子数组前缀上 构建左子树。 递归地在最大值 右边 的 子数组后缀上 构建右子树。

代码

class Solution {
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return Traversal(nums, 0, nums.size() - 1);
    }

    TreeNode* Traversal(const vector<int>& nums, int left, int right){
        if(left > right) return nullptr;
        int maxIdx = left;
        for(int i = left;i<=right;i++){
            if(nums[i] > nums[maxIdx]){
                maxIdx = i;
            }
        }
        TreeNode* head = new TreeNode(nums[maxIdx]);
        head->left = Traversal(nums, left, maxIdx - 1);
        head->right = Traversal(nums, maxIdx + 1, right);
        return head;
    }
};

16.合并二叉树

题目链接:617.合并二叉树

给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

思路

利用递归来实现,让两棵树root1和root2同时前序遍历。

每次递归过程中,只有两棵树当前节点均为nullptr时才直接return,否则按照题目规则,如果两棵树当前节点均不为空,则将两个节点的值累加,利用累加值构建新树的节点,如果只有一棵树当前节点不为空,则用这个不为空的节点的值构建新树的节点。

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(root1 == nullptr && root2 == nullptr) return nullptr;
        int cur_val;
        if(root1 != nullptr && root2 != nullptr){
            cur_val = root1->val + root2->val;
        }
        else if(root1 != nullptr && root2 == nullptr){
            cur_val = root1->val;
        }
        else{
            cur_val = root2->val;
        }
        TreeNode* cur = new TreeNode(cur_val);
        cur->left = mergeTrees(root1 == nullptr ? nullptr : root1->left, root2 == nullptr ? nullptr : root2->left);
        cur->right = mergeTrees(root1 == nullptr ? nullptr : root1->right, root2 == nullptr ? nullptr : root2->right);
        return cur;
    }
};

17.二叉搜索树中的搜索

题目链接:700.二叉搜索树中的搜索

给定二叉搜索树(BST)的根节点 root 和一个整数值 val

你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

思路

可采用最基本的递归查询。

因为root是一棵二叉搜索树,在每一次递归判断过程中,如果val等于当前子树的节点值,则直接返回该节点;如果val小于当前子树的节点值,则递归往当前子树的左子树寻找;如果val大于当前子树的节点值,则递归往当前子树的右子树寻找。如果最终没有找到,返回空指针。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if(!root) return root;
        if(val == root->val) return root;
        else if(val < root->val) return searchBST(root->left, val);
        else return searchBST(root->right, val);
    }
};

18.验证二叉搜索树

题目链接:98.验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。

class Solution {
public:
    int findRight(TreeNode* node){
        while(node->right) node = node->right;
        return node->val;
    }

    int findLeft(TreeNode* node){
        while(node->left) node = node->left;
        return node->val;
    }

    bool isValidBST(TreeNode* root) {
        if(!root || (!root->left && !root->right)) return true;
        bool left_success = isValidBST(root->left);
        bool right_success = isValidBST(root->right);

        if(!left_success || !right_success) return false;
        
        return (!root->left || root->val > findRight(root->left)) && (!root->right || root->val < findLeft(root->right));
    }
};

19.二叉搜索树的最小绝对差

题目链接:530.二叉搜索树的最小绝对差

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

思路

对于一棵二叉搜索树进行中序遍历,可以得到单调递增的序列。因此要想得到二叉搜索树的最小绝对差,可以对该二叉树进行中序遍历,然后判断得到的序列的最小相邻值。

// 递归实现
class Solution {
public:
    TreeNode* pre = nullptr;

    int getMinimumDifference(TreeNode* root) {
        int minDistance = numeric_limits<int>::max();
        Traversal(root, minDistance);
        return minDistance;
    }

    void Traversal(TreeNode* node, int& minDistance){
        if(node == nullptr) return;
        Traversal(node->left, minDistance);
        if(pre){
            minDistance = min(minDistance, abs(node->val - pre->val));
        }
        pre = node;
        Traversal(node->right, minDistance);
    }
};
// 迭代实现
class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        TreeNode* pre = nullptr;
        int minDistance = numeric_limits<int>::max();

        stack<TreeNode*> stk;
        TreeNode* cur = root;

        while(!stk.empty() || cur){
            if(cur){
                stk.push(cur);
                cur = cur->left;
            }
            else{
                TreeNode* tmp = stk.top();
                stk.pop();
                if(pre) minDistance = min(minDistance, abs(tmp->val - pre->val));
                pre = tmp;

                cur = tmp->right;
            }
        }

        return minDistance;
    }
};

20.二叉搜索树中的众数

题目链接:501.二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

结点左子树中所含节点的值 小于等于 当前节点的值 结点右子树中所含节点的值 大于等于 当前节点的值 左子树和右子树都是二叉搜索树

思路

最简单的思路,创建一个哈希表,然后中序遍历二叉搜索树,统计每个数值的出现频率,取最高频率的数值返回。

另外,由于题目中要处理的是二叉搜索树,那么就可以根据二叉搜索树的性质,中序遍历,统计连续数值的最长值,将连续最长的数值返回。

// 简单思路
class Solution {
public:
    vector<int> findMode(TreeNode* root) {
        unordered_map<int,int> hashMap;
        stack<TreeNode*> stk;
        TreeNode* cur = root;
        while(!stk.empty() || cur){
            if(cur){
                stk.push(cur);
                cur = cur->left;
            }
            else{
                TreeNode* tmp = stk.top();
                stk.pop();
                hashMap[tmp->val] += 1;
                cur = tmp->right;
            }
        }
        int maxFreq = numeric_limits<int>::min();
        for(const auto& q: hashMap){
            maxFreq = max(maxFreq, q.second);
        }
        vector<int> results;
        for(const auto& q: hashMap){
            if(q.second == maxFreq) results.push_back(q.first);
        }
        return results;
    }
};

// 不利用额外空间的解法
class Solution {
private:
    TreeNode* pre = nullptr;
    int count = 1;
    int maxCount;
    vector<int> results;

    void Traversal(TreeNode* node){
        if(node == nullptr) return;
        Traversal(node->left);
        if(pre){
            if(node->val == pre->val){
                count++;
            }
            else{
                count = 1;
            }
        }
        pre = node;
        if(count > maxCount){
            maxCount = count;
            results.clear();
            results.push_back(node->val);
        }
        else if(count == maxCount){
            results.push_back(node->val);
        }
        Traversal(node->right);
    }
public:
    vector<int> findMode(TreeNode* root) {
        maxCount = 1;
        Traversal(root);
        return results;
    }
};

21. 二叉树的最近公共祖先

题目链接:236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路

对于节点p、q,分别找到从根节点root到p、q的路径,用数组存储两条路径,并将路径反转,然后找到两条路径中交叠的第一个节点,交叠的第一个节点便是两个指定节点的最近公共祖先。

class Solution {
public:
    bool findPath(TreeNode* node, int target, vector<TreeNode*>& path){
        if(!node) return false;
        if(node->val == target){
            path.push_back(node);
            return true;
        }
        path.push_back(node);
        bool leftSuccess = findPath(node->left, target, path);
        bool rightSuccess = findPath(node->right, target, path);
        if(leftSuccess || rightSuccess){
            return true;
        }
        path.pop_back();
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> path1, path2;
        findPath(root, p->val, path1);
        findPath(root, q->val, path2);
        reverse(path1.begin(), path1.end());
        reverse(path2.begin(), path2.end());

        for(int i = 0;i<path1.size();i++){
            for(int j = 0;j<path2.size();j++){
                if(path1[i]->val == path2[j]->val){
                    return path1[i];
                }
            }
        }
        return nullptr;
    }
};

22. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路

可以采用回溯的方法,后序遍历,具体的解释过程可以参考,与236二叉树的最近公共祖先类似的解题思路。

或者,我们可以利用二叉搜索树的性质。如果节点g是p和q的公共祖先,那么节点g的值一定在p和q之间,在从上往下遍历时,找到的第一个值在p和q之间的节点g,就是p和q的最小公共祖先,如果这时再往g的左右子树遍历,这时就不再是p和q的最小公共祖先。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q || root == nullptr) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if(left != nullptr && right != nullptr) return root;
        else if(left != nullptr && right == nullptr) return left;
        else if(left == nullptr && right != nullptr) return right;
        else return nullptr;
    }
};

// 递归法,利用二叉搜索树性质
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr) return root;
        if(root->val < p->val && root->val < q->val){
            TreeNode* right = lowestCommonAncestor(root->right, p, q);
            if(right != nullptr) return right;
        }
        if(root->val > p->val && root->val > q->val){
            TreeNode* left = lowestCommonAncestor(root->left, p, q);
            if(left != nullptr) return left;
        }
        return root;
    }
};

// 迭代法,利用二叉搜索树性质
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr) return root;
        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 root;
    }
};

23.二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

思路

插入最后一定会落到叶子上,二分查找

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(!root) return new TreeNode(val);
        TreeNode* cur = root;
        while(cur){
            if(cur->val < val){
                if(cur->right) cur = cur->right;
                else{
                    TreeNode* newNode = new TreeNode(val);
                    cur->right = newNode;
                    break;
                }
            }
            else if(cur->val > val){
                if(cur->left) cur = cur->left;
                else{
                    TreeNode* newNode = new TreeNode(val);
                    cur->left = newNode;
                    break;
                }
            }
        }
        return root;
    }
};

24.删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

思路

首先找到待删除的节点,然后根据情况进行删除操作,一共有五种情况。

第一种情况:没找到删除的节点,遍历到空节点直接返回了 找到删除的节点 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
        if (root->val == key) {
            // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
            if (root->left == nullptr && root->right == nullptr) {
                ///! 内存释放
                delete root;
                return nullptr;
            }
            // 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点
            else if (root->left == nullptr) {
                auto retNode = root->right;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
            else if (root->right == nullptr) {
                auto retNode = root->left;
                ///! 内存释放
                delete root;
                return retNode;
            }
            // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置
            // 并返回删除节点右孩子为新的根节点。
            else {
                TreeNode* cur = root->right; // 找右子树最左面的节点
                while(cur->left != nullptr) {
                    cur = cur->left;
                }
                cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置
                TreeNode* tmp = root;   // 把root节点保存一下,下面来删除
                root = root->right;     // 返回旧root的右孩子作为新root
                delete tmp;             // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)
                return root;
            }
        }
        if (root->val > key) root->left = deleteNode(root->left, key);
        if (root->val < key) root->right = deleteNode(root->right, key);
        return root;
    }
};

25. 修剪二叉搜索树

给你二叉搜索树的根节点 root ,同时给定最小边界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);
        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;
    }
};

26.将有序数组转换为二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

思路

采用递归的方法来实现。每次都选择mid作为根节点

class Solution {
public:
    TreeNode* traversal(const vector<int>& nums, int left, int right){
        if(left > right) return nullptr;
        int mid = left + (right - left) / 2;
        TreeNode* node = new TreeNode(nums[mid]);
        node->left = traversal(nums, left, mid - 1);
        node->right = traversal(nums, mid + 1, right);
        return node;
    }

    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return traversal(nums, 0, nums.size() - 1);
    }
};

27.把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。

思路

采用一种右中左的反向中序遍历,注意,这里与通常的左中右的遍历不同。在反向中序遍历的过程中,记录下前一个遍历到的节点pre,将当前节点node的值加上pre节点的值。

类似地,如果想要求这样的累加树:每个节点node的新值等于原树种小于或等于node.val的值之和,可以采用正向的中序遍历来解决。

class Solution {
public:
    TreeNode* pre = nullptr;

    void traversal(TreeNode* node){
        if(!node) return;
        traversal(node->right);
        if(pre) node->val += pre->val;
        pre = node;
        traversal(node->left);
    }

    TreeNode* convertBST(TreeNode* root) {
        traversal(root);
        return root;
    }
};