二叉树
二叉树概念和性质
104.二叉树的的最大深度(递归)
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
思路 :利用递归,深度优先搜索,调用递归函数看结点的左子树和右子树中哪个更深,直到遇到叶子结点
官方题解
代码
/**
* 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:
int maxDepth(TreeNode* root) {
//深度优先搜索
if(!root)
return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
98. 验证二叉搜索树(中序遍历)
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
解法一 用一个数组存放经过中序遍历的结点值 - 由于要求所有的左子树小于当前结点所有右子树大于当前结点,因此可以利用中序遍历,将二叉树线性化,得到一个序列,后面只需判断这个序列是否严格升序就好了(不能有相同元素)
- 时间空间都是O(N)
代码
/**
* 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 isValidBST(TreeNode* root) {
//中序遍历得到的序列
vector<int>treeNum;
//对二叉树进行中序遍历
InOrderTraverse(root,treeNum);
//判断是否升序
for(int i=1;i<treeNum.size();i++)
{
if(treeNum[i]<=treeNum[i-1])
return false;
}
return true;
}
//中序遍历(左根右)
void InOrderTraverse(TreeNode * root,vector<int>&arr)
{
if(root)
{
//递归调用
InOrderTraverse(root->left,arr);
arr.push_back(root->val);
InOrderTraverse(root->right,arr);
}
}
};
解法二 在中序遍历过程中直接判断是否有序(来自题解【代码随想录】)
- 不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
- 要定义一个
long long
的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int
最小值,所以定义为long long
的类型,初始化为long long
最小值。 - 终止条件:节点为空时,返回
true
- 单层递归逻辑:中序遍历,一直更新
maxVal
,一旦发现maxVal >= root->val
,就返回false
,注意元素相同时候也要返回false。
代码
class Solution {
public:
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val;
else return false;
bool right = isValidBST(root->right);
return left && right;
}
};
101. 对称二叉树(代码比较精巧,不好理解)
给定一个二叉树,检查它是否是镜像对称的。
注意:
上图中下面的那个示例,说明即使数字是对称的,但是所在的位置不是镜像对称的也不是对称二叉树,所以不能只考虑比较结点值,而应注意是否为对称位,因此不能验证通过中序遍历得到的数组是否是回文串来判断是否为对称二叉树。
思路: (官方题解)
- 利用一个递归函数
check
,通过同步移动两个指针的方法来遍历这棵树 - p指针和q指针一开始都指向根节点,随后p往左子树跳时,q往右子树跳;p往右子树跳时,q往左子树跳。
- 每次跳都检查当前p和q的结点值是否相等,以及是否对称(当有一个为空而另外一个不为空时说明不对称)。
代码
class Solution {
public:
bool check(TreeNode *p, TreeNode *q) {
//都为空时返回true
if (!p && !q) return true;
//只有一个为空时,说明不对称,返回false
if (!p || !q) return false;
//比较当前结点值,以及利用递归遍历整棵树
return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
102. 二叉树的层序遍历(中等,参考题解,自己码的代码)
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
思路:
- 由于需要逐层遍历结点,所以想到要用广度优先遍历,利用队列实现(之前学数据结构的时候学到的,还好还记得)
- 但是本题不能用普通的广度优先遍历得到一个一维数组,而是要得到一个根据层序排列的二维数组,这里我参考了官方题解的做法,就是:
- 首先根元素入队
- 当队列不为空的时候
- 求当前队列的长度
size
- 依次从队列中取
size
个元素进行拓展(将结点的左子树和右子树压到队尾,如果有的话),然后进入下一层的迭代
- 也就是说,每次进行拓展的
size
个元素是属于同一层的,将这些数用一维数组存起来,每层拓展完成后将这个数组加到二维数组中去。
代码
/**
* 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:
vector<vector<int>> levelOrder(TreeNode* root) {
if(!root) return{};
vector<vector<int>>result;
vector<int>tmp;
TreeNode* node;
//利用队列
queue<TreeNode*>que;
//放入根结点
que.push(root);
while(!que.empty())
{
//求出每层的节点数
int queSize=que.size();
//展开que中queSize个元素(属于同一层的元素)
for(int i=0;i<queSize;i++)
{
//取出队列中的第一个元素
node=que.front();
//然后将它弹出
que.pop();
//将节点值插入数组
tmp.push_back(node->val);
//展开当前节点的坐左子树和右子树(如果有的话)
//将左孩子和右孩子节点压到队列尾部
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
//向结果数组压入二叉树每层的节点值
result.push_back(tmp);
tmp.clear();
}
return result;
}
};
108. 将有序数组转换为二叉搜索树(递归)
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
思路:
- 主要原理是将一个中序数组构造成搜索二叉树,且满足高度平衡。
- 利用一个递归函数实现,每次递归都将数组中间位置的值作为根节点(如果数组长为偶数,那就取中间靠左的那个)。
- 新的根节点需要创建在堆区,否则不能返回它的地址。
- 左半段数组同样是一个中序数组,继续递归,作为左子树,右半段数组同。
- 关键是如何截取数组
代码
/**
* 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* sortedArrayToBST(vector<int>& nums)
{
//借助一个递归函数来实现
return helper(nums,0,nums.size()-1);
}
TreeNode * helper(vector<int>& nums,int left,int right)
{
//终止条件
if(left>right)
return nullptr;
//中序序列构造搜索二叉树
//每次取数组中点作为根节点
int mid=(left+right)/2;
//递归调用,创建一个新节点,左子树为数组左半段,右子树为数组右半段
TreeNode* root=new TreeNode(nums[mid],helper(nums,left,mid-1),helper(nums,mid+1,right));
//返回根节点地址(开辟在堆区)
return root;
}
};
剑指 Offer 04. 二维数组中的查找(二叉搜索树)
在一个 n * m
的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:
- 矩阵
matrix
有非常特殊的排列方式,从上到下递增,从左到右递增。 - 那么对于最左下和最右上的元素,可将其分别作为根节点,将矩阵看做一个二叉搜索树,
15
左边的元素更小,右边元素更大;18
上边元素更小,右边元素更大。
- 将矩阵上建立直角坐标系,假如从
18
那个位置(坐标原点)开始搜索,假如小于目标target=5
,则将搜索位置向右移动,大于则向上移动
代码
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
//注意异常判断
if(matrix.empty())
return 0;
int m=matrix.size(),n=matrix[0].size();
//从最左下开始搜索
int i=m-1,j=0;
while(i>=0&&j<n)
{
if(target<matrix[i][j])
i--; //向上
else if(target>matrix[i][j])
j++; //向右
else
return true;
}
return false;
}
剑指 Offer 07. 重建二叉树(递归,二叉树遍历)
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路
- 先序遍历的第一个结点为根,在中序遍历中确定根节点的位置
- 根据中序遍历的特点,中序根节点前的为左子树,后边的为右子树
- 由此可以得到先序遍历中的左子树和右子树,然后递归,再从左右子树中确定根节点以及他们的左右子树
- 注意:递归实现中,不要忘了终止条件!!!
- 左右子树的边界要仔细想想
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
//递归构建,后面四个参数分别为前序和中序遍历的左右界
TreeNode* mybuild(vector<int>& preorder, vector<int>& inorder,unordered_map<int,int>& index,int pre_left,int pre_right,int in_left,int in_right)
{
//不要忘了递归终止条件!!!
if(pre_left>pre_right)
return NULL;
//先序遍历中的根节点
int pre_root=preorder[pre_left];
//构建根节点
TreeNode* root=new TreeNode(pre_root);
//根据先序根节点找中序遍历根节点
int in_root=index[pre_root];
//根据中序根节点计算左子树长度
int left_size=in_root-in_left;
//构建左子树,最后四个参数分别为先序和中序遍历中的左子树部分
root->left=mybuild(preorder,inorder,index,pre_left+1,pre_left+left_size,in_left,in_root-1);
//构建右子树,后四个参数分别为先序和中序遍历中的右子树部分
root->right=mybuild(preorder,inorder,index,pre_left+left_size+1,pre_right,in_root+1,in_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n=preorder.size();
unordered_map<int,int>index;
for(int i=0;i<inorder.size();i++)
{
index[inorder[i]]=i;
}
TreeNode* head=mybuild(preorder,inorder,index,0,n-1,0,n-1);
return head;
}
};
617. 合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
思路:
- 递归,深度优先
- 如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,用一个新节点作为合并后的节点,而不是修改原来的节点值
- 如果有一个为空,则将另外一个不为空的节点作为新二叉树的节点
- 如果两个节点都为空,就返回空NULL
代码
/**
* 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* mergeTrees(TreeNode* root1, TreeNode* root2) {
//如果两节点均非空
if(root1!=NULL&&root2!=NULL)
{
int m_val=root1->val+root2->val;
//构造一个新节点,值为重复节点的值的和
TreeNode* newRoot=new TreeNode(m_val);
//递归构造左右子树
newRoot->left=mergeTrees(root1->left,root2->left);
newRoot->right=mergeTrees(root1->right,root2->right);
return newRoot;
}
//有一个为空
else if(root1==NULL)
{
return root2;
}
else if(root2==NULL)
{
return root1;
}
//都为空
return NULL;
}
};
复杂度
时间O(min(m,n))
,m
和n
分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会对该节点进行显性合并操作,因此被访问到的节点数不会超过较小的二叉树的节点数。空间O(min(m,n))
。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。
116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点(完全二叉树)。
填充它的每个next
指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next
指针设置为 NULL
。
初始状态下,所有 next
指针都被设置为NULL
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
解法一
- 层次遍历,用辅助队列,空间O(N)
- 层次遍历基于广度优先搜索,它与广度优先搜索的不同之处在于,广度优先搜索每次只会取出一个节点来拓展,而层次遍历会每次将队列中的所有元素都拿出来拓展,这样能保证每次从队列中拿出来遍历的元素都是属于同一层的
- 注意进行异常输入判断,检查输入是否为空指针
class Solution {
public:
Node* connect(Node* root) {
//广度优先搜索,层次遍历
queue<Node*>Q;
int n=0;
if(root==NULL)
return root;
Q.push(root);
while(!Q.empty())
{
int size=Q.size();
for(int i=0;i<size;i++)
{
Node* tmp=Q.front();
Q.pop();
if(i!=size-1)
tmp->next=Q.front();
if(tmp->left!=NULL)
Q.push(tmp->left);
if(tmp->right!=NULL)
Q.push(tmp->right);
}
n++;
}
return root;
}
};
解法二
- 使用已建立的
next 指针
,迭代,思路见官方题解 - 空间O(1)
解法三
- 思路同解法二,只是用的递归实现