1.完全二叉树的节点个数(222题)
题目描述:给出一个完全二叉树,求出该树的节点个数。
思路:按照普通二叉树来处理 就是和求二叉树的深度类似的题目
cpp
class Solution {
public:
//递归函数
int getnum(TreeNode* node){
if(node == NULL)return 0;//递归的终止条件
int leftnum = getnum(node->left);//左
int righrnum = getnum(node->right);//右
int treenum = leftnum + righrnum + 1;//中
return treenum;
}
int countNodes(TreeNode* root) {
return getnum(root);
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(log n)
迭代法:(层序遍历)
cpp
class Solution {
public:
int countNodes(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
int result = 0;
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
result++; // 记录节点数量
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return result;
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(n)
完全二叉树方法:完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树
cpp
class Solution {
public:
int countNodes(TreeNode* root) {
if(root == NULL)return 0;
//左右子树的深度来判断是否是满二叉树
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftdepth = 0,rightdepth = 0;
//左子树
while(left){
left = left->left;
leftdepth++;
}
//右子树
while(right){
right = right->right;
rightdepth++;
}
//如果是满二叉树则返回节点数量
if(leftdepth == rightdepth){
return (2 << leftdepth) - 1;
}
int leftnum = countNodes(root->left);//左
int rightnum = countNodes(root->right);//右
int result = leftnum +rightnum+ 1;//中
return result;
}
};
- 时间复杂度:O(log n × log n)
- 空间复杂度:O(log n)
2.平衡二叉树(110题)
题目描述:
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
思路:递归法(后序遍历)
cpp
class Solution {
public:
//迭代法后序遍历
int getdepth(TreeNode* node){
if(node == NULL)return 0;//终止条件
int leftdepth = getdepth(node->left);//左子树
if(leftdepth == -1)return -1;//左子树不是平衡二叉树
int rightdepth = getdepth(node->right);//右子树
if(rightdepth == -1)return -1;//右子树不是平衡二叉树
int result;//结果
//中间处理
if(abs(leftdepth - rightdepth)>1){
result = -1;//左右子树的高度差大于一不是平衡二叉树
}else{
result = 1 + max(leftdepth,rightdepth);//返回结果
}
return result;
}
//判断高度是否为平衡二叉树
bool isBalanced(TreeNode* root) {
return getdepth(root) == -1 ? false : true;//等于-1代表不是平衡二叉树
}
};
迭代法:我们需要迭代法来计算高度在做后序处理所以比较麻烦实用前序遍历,使用了两个栈来完成内存消耗大,
cpp
class Solution {
private:
//定义一个求得深度的函数
int getdepth(TreeNode* node){
stack<TreeNode*>st;//栈
if(node != NULL)st.push(node);
int depth = 0;//记录深度
int result = 0;//记录结果
while(!st.empty()){
TreeNode* cur = st.top();//压入栈
//不是空节点执行迭代
if(cur != NULL){
st.pop();
st.push(cur);
st.push(NULL);//插入空节点,使用统一迭代方式
depth++;
if(cur->right)st.push(cur->right);//右
if(cur->left)st.push(cur->left);//左
}else{
st.pop();//弹出空节点
cur = st.top();//
st.pop();//弹出元素
depth--;
}
result = result > depth ? result : depth;//结果判断
}
return result;
}
public:
bool isBalanced(TreeNode* root) {
stack<TreeNode*>st;//栈
if(root == NULL)return true;
st.push(root);
while(!st.empty()){
TreeNode* node = st.top();
st.pop();
//中真正的左右子树的深度来做判断
if(abs(getdepth(node->left)-getdepth(node->right))>1){
return false;
}
if(node->right)st.push(node->right);//右
if(node->left)st.push(node->left);//左
}
return true;
}
};
3.二叉树的所有路径(257题)
题目描述:给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
思路:从根节点到叶子的路径,需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。本题需要记录路径,然后再回溯,
递归法:当 cur不为空,其左右孩子都为空的时候,就找到叶子节点,因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进path中,回溯和递归是一一对应的,有一个递归,就要有一个回溯,
cpp
class Solution {
private:
//前序遍历,path路径,result结果
void travelsal(TreeNode* cur,vector<int>& path,vector<string>& result){
path.push_back(cur->val);//中
//终止条件,当前节点不为空,左右孩子为空终止,叶子节点
if(cur->left == NULL && cur->right == NULL){
string spath;//
//将path原来整数转换为字符型在加上->
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){
travelsal(cur->left,path,result);
path.pop_back();//回溯
}
//右
if(cur->right){
travelsal(cur->right,path,result);
path.pop_back();//回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<int>path;//路径
vector<string>result;//结果
if(root == NULL)return result;
travelsal(root,path,result);
return result;
}
};
迭代法:需要两个栈来实现,一个栈递归,一个栈来存放路径
cpp
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
stack<TreeNode*>treest;//保存树的遍历节点
stack<string>pathst;//路径节点
vector<string>result;//结果
if(root == NULL)return result;
treest.push(root);
pathst.push(to_string(root->val));
while(!treest.empty()){
TreeNode* node = treest.top();//取出节点,中
treest.pop();
string path = pathst.top();//取出该节点对应的路径
pathst.pop();
//遇到叶子节点
if(node->left == NULL && node->right == NULL){
result.push_back(path);
}
//右
if(node->right){
treest.push(node->right);
pathst.push(path+"->"+to_string(node->right->val));
}
//左
if(node->left){
treest.push(node->left);
pathst.push(path+"->"+to_string(node->left->val));
}
}
return result;
}
};
4.左叶子之和(404题)
题目描述:计算给定二叉树的所有左叶子之和。
思路:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点,判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。
递归法:后序遍历,注意终止条件和左根节点处理
cpp
class Solution {
public:
//采用的是后序遍历
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL)return 0;//终止条件
if(root->left == NULL && root->right == NULL)return 0;//终止条件
int leftvalue = sumOfLeftLeaves(root->left);//左
//左节点不为空且左节点的左右节点都为空 才是左根节点
if(root->left != NULL && root->left->left == NULL && root->left->right == NULL){
leftvalue = root->left->val;
}
int rightvalue = sumOfLeftLeaves(root->right);//右
int result = leftvalue + rightvalue;//结果
return result;
}
};
迭代法:前序遍历,迭代模版套用即可实现
cpp
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->right)st.push(node->right);
if(node->left)st.push(node->left);
}
return result;
}
};
不能根据该节点判断是否是左根节点,只能根据父节点来判断 ,之前通过节点的左右孩子判断本节点的属性
5.找树左下角的值(513题)
题目描述:给定一个二叉树,在树的最后一行找到最左边的值。
递归法: 首先要是最后一行,然后是最左边的值,深度最大的叶子节点一定是最后一行,保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
cpp
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--;//回溯
}
return ;
}
int findBottomLeftValue(TreeNode* root) {
traversal(root,0);
return result;
}
};
迭代法:(层序遍历)只需要记录最后一行第一个节点的数值就可以了
cpp
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();
for(int i = 0;i < size;i++){
TreeNode* node = que.front();
que.pop();
if(i == 0)result = node->val;
if(node->left)que.push(node->left);
if(node->right)que.push(node->right);
}
}
return result;
}
};
涉及递归求深度,使用前序遍历需要回溯(前中后序都可以,强调左在右的前面即可),层序遍历的迭代方法,
6. 路径总和(112题)
题目描述:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。说明: 叶子节点是指没有子节点的节点
递归法:前中后序,中间节点不需处理,
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
cpp
class Solution {
private:
//这里不用处理中间节点,只需要处理左节点,之后去处理右节点
bool traversal(TreeNode* cur,int count){
if(!cur->left && !cur->right && count == 0)return true;//叶子节点,节点总和值相等就返回正确
if(!cur->left && !cur->right)return false;//叶子节点,但是节点值不返回错误
//左
if(cur->left){
count -= cur->left->val;
if(traversal(cur->left,count))return true;//找到了节点值,返回
count += cur->left->val;//回溯
}
//右
if(cur->right){
count -= cur->right->val;
if(traversal(cur->right,count))return true;//找到节点,返回
count += cur->right->val;//回溯
}
return false;
}
public:
//实现函数
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == NULL)return false;
return traversal(root,targetSum-root->val);//递归函数
}
};
迭代法: 用栈迭代(前序遍历)栈内存放的是pair<节点指针,路径数值>
cpp
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == NULL)return false;
stack<pair<TreeNode*,int>>st;//栈内存放的是pair<节点指针,路径数值>
st.push(pair<TreeNode* ,int>(root,root->val));
while(!st.empty()){
pair<TreeNode*,int>node = st.top();
st.pop();
//如果该节点是叶子节点,同时该节点的路径数值等于目标值,返回true
if(!node.first->left&&!node.first->right&&targetSum == node.second)return true;
//右节点,一个节点入栈后,记录该节点的路径数值
if(node.first->right){
st.push(pair<TreeNode*,int>(node.first->right,node.second+node.first->right->val));
}
//左节点,一个节点入栈后,记录该节点的路径数值
if(node.first->left){
st.push(pair<TreeNode*,int>(node.first->left,node.second+node.first->left->val));
}
}
return false;
}
};
路径总和ii(113题)
题目描述:
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
递归法:需要遍历整棵树,不需要返回值
cpp
class Solution {
private:
vector<int>path;
vector<vector<int>>result;
//递归函数不需要返回值,遍历整颗树
void traversal(TreeNode* cur,int count){
//遇到叶子节点且找到了和为目标值的路径
if(!cur->left&&!cur->right&&count==0){
result.push_back(path);
return;
}
//遇到叶子节点没找到合适边返回
if(!cur->left&&!cur->right)return;
//左
if(cur->left){
path.push_back(cur->left->val);
count -= cur->left->val;
traversal(cur->left,count);
count += cur->left->val;
path.pop_back();
}
//右
if(cur->right){
path.push_back(cur->right->val);
count -= cur->right->val;
traversal(cur->right,count);//递归
count += cur->right->val;//回溯
path.pop_back();//回溯
}
return ;
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
result.clear();
path.clear();
if(root == NULL)return result;
path.push_back(root->val);//把根节点放到路径中
traversal(root,targetSum-root->val);
return result;
}
};
7.从中序与后序遍历序列构造二叉树(106题)
根据一棵树的中序遍历与后序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
递归法:以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
首先判断是否是空节点,再看是否是叶子节点,查找切割点,分中序数组,再分后序数组,递归
切割要注意循环不变量原则,左闭右开,就是中序数组大小一定是和后序数组的大小相同的
cpp
class Solution {
private:
//使用数组切割的方法来实现,对中序数组进行切割,再对后序数组切割
TreeNode* traversal(vector<int>& inorder,vector<int>& postorder){
//空节点
if(postorder.size() == 0)return NULL;
int rootval = postorder[postorder.size()-1];//后序数组最后一个元素是根节点
TreeNode* root = new TreeNode(rootval);//创建一个树
if(postorder.size() == 1)return root;//叶子节点
int deliindex ;//查找切割点,在中序数组中查找
for(deliindex = 0;deliindex < inorder.size();deliindex++){
if(inorder[deliindex] == rootval)break;
}
//对中序数组进行切割,左闭右开切割
vector<int>leftinorder(inorder.begin(),inorder.begin()+deliindex);
vector<int>rightinorder(inorder.begin()+deliindex+1,inorder.end());
postorder.resize(postorder.size()-1);//舍弃末尾元素
//因为中序数组长度和后序数组长度相等,这里使用中序左右数组长度来进行切割
vector<int>leftpostorder(postorder.begin(),postorder.begin()+leftinorder.size());
vector<int>rightpostorder(postorder.begin()+leftinorder.size(),postorder.end());
root->left = traversal(leftinorder,leftpostorder);
root->right = traversal(rightinorder,rightpostorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0 || postorder.size() == 0)return NULL;
return traversal(inorder,postorder);
}
};
从前序与中序遍历序列构造二叉树(105题)
题目描述:
根据一棵树的前序遍历与中序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
递归法:
cpp
class Solution {
private:
//切割和上一个题一样原理,这里使用下标的方法实现
TreeNode* traversal(vector<int>& inorder,int inorderbegin,int inorderend,vector<int>& preorder,int preorderbegin,int preorderend){
if(preorderbegin == preorderend)return NULL;//空节点
int rootval = preorder[preorderbegin];//根节点值
TreeNode* root = new TreeNode(rootval);//创建一个树
if(preorderend-preorderbegin == 1)return root;//叶子节点
int deliindex;//寻找切割点
for(deliindex = inorderbegin;deliindex < inorderend;deliindex++){
if(inorder[deliindex] == rootval)break;//找到了就退出
}
//切割中序数组,左数组,右数组
int leftinorderbegin = inorderbegin;
int leftinorderend = deliindex;
int rightinorderbegin = deliindex+1;
int rightinorderend = inorderend;
//切割前序数组,左数组,右数组,这里注意使用长度
int leftpreorderbegin = preorderbegin+1;
int leftpreorderend = preorderbegin + 1 + leftinorderend - leftinorderbegin;
int rightpreorderbegin = preorderbegin + 1 + leftinorderend - leftinorderbegin;
int rightpreorderend = preorderend;
//递归,左,右
root->left = traversal(inorder,leftinorderbegin,leftinorderend,preorder,leftpreorderbegin,leftpreorderend);
root->right = traversal(inorder,rightinorderbegin,rightinorderend,preorder,rightpreorderbegin,rightpreorderend);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(inorder.size() == 0 || preorder.size() == 0)return NULL;
return traversal(inorder,0,inorder.size(),preorder,0,preorder.size());
}
};
注意:前序和中序,后序和中序可以确定一颗二叉树,但是前序和后序不可以确定一颗唯一二叉树,
8.最大二叉树(654题)
题目描述:
给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:
- 二叉树的根是数组中的最大元素。
- 左子树是通过数组中最大值左边部分构造出的最大二叉树。
- 右子树是通过数组中最大值右边部分构造出的最大二叉树。
通过给定的数组构建最大二叉树,并且输出这个树的根节点。
递归法:构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
cpp
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* node = new TreeNode(0);//创建一个树的节点
//终止条件,叶子节点,数组大小为1
if(nums.size() == 1){
node->val = nums[0];//节点值
return node;
}
int maxvalue = 0;//最大值
int maxvalueindex = 0;//最大值下标
//中的处理逻辑
for(int i = 0;i < nums.size();i++){
//更新最大值
if(nums[i] > maxvalue){
maxvalue = nums[i];//找到最大值
maxvalueindex = i;//下标
}
}
node->val = maxvalue;//节点值是最大值,根节点
//左处理逻辑,需要判断是否左边有值
if(maxvalueindex > 0){
vector<int>newvc(nums.begin(),nums.begin()+maxvalueindex);//新的数组
node->left = constructMaximumBinaryTree(newvc);
}
//右处理逻辑,需要判断右边是否有值
if(maxvalueindex < (nums.size()-1)){
vector<int>newvc(nums.begin()+maxvalueindex+1,nums.end());
node->right = constructMaximumBinaryTree(newvc);
}
return node;
}
};
注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
总结:
完全二叉树节点个数:按照普通二叉树来处理就是深度递归的逻辑,使用后序遍历就可以实现,迭代法:很好理解使用层序遍历既可实现,满二叉树有节点的计算方法,知道深度,就可以使用公式来计算,所以就变成了处理深度的问题,最左层深度和最右层深度一样大满二叉树,
平衡二叉树:知道平衡二叉树的定义,左右子树高度相差值为1,且左右子树都需要为平衡二叉树,所以递归终止条件需要注意,后序遍历,也就变成了求最大深度的问题了,就是高度,后序遍历,迭代法需要两个栈来实现,一个栈求深度,另一个栈迭代
二叉树的所有路径:根节点到叶子节点,所以需要回溯,也要记录值,立即返回,需要返回值,记住回溯递归一一对应,注意需要回溯,迭代需要两个栈来完成,一个栈递归,另一个存路径
左叶子之和:所有左叶子节点的和,后序遍历,注意终止条件和左叶子节点处理,找到根节点,不能根据该节点判断是否是左根节点,只能根据父节点来判断,前序遍历迭代模版套用即可
找树左下角的值:递归法:首先要是最后一行,然后是最左边的值,深度最大的叶子节点一定是最后一行,保证优先左边搜索,然后记录深度最大的叶子节点,注需要回溯,层序遍历,取最后一层的第一个元素就可
路径总和:递归需要回溯,注意要到叶子节点终止条件,递归过程中如果发现了符合条件直接返回,目标值减去叶子节点,最后判断是否为0更好操作,用栈来迭代的话需要键值对,存放节点指针和路径数值,路径总和2:这是需要遍历整颗树的路径,这时候需要数组来接收路径,切记压入数组和,目标缩减需要回溯,
从后序和中序遍历构造二叉树,以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。首先判断是否是空节点,再看是否是叶子节点,查找切割点,分中序数组,再分后序数组,递归切割要注意循环不变量原则,左闭右开,就是中序数组大小一定是和后序数组的大小相同的,需要定义四个数组,首先需要后序遍历根节点,然后再根据根节点在中序的位置进行切割,切割成左和右,然后再根据中序数组和后序数组切割的长度相等,前序和中序,前序确定根节点
最大二叉树:构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树,最好通过下标来实现