110. 平衡二叉树
拿到这题,我们首先要明确平衡二叉树的概念:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1
既然要判断它是不是平衡二叉树,我们就要遍历这棵树,我们这里采用递归的方式来遍历
遇到要使用递归函数的地方,先上递归三部曲!
1.确定函数返回值和参数:
我们要看它是不是平衡二叉树,我们就要得到他每一条分支的深度,所以我建议将返回值设置为int,而其参数,我们暂定为TreeNode*
2.确定递归函数的终止条件:
if(node==nullptr){
return 0;
}
3.确定单层递归的逻辑:
要确定逻辑,我们就要确定我们遍历二叉树的方式(前中后序遍历)的哪种方式,这里我采用的是后序遍历的方式。
我们应该在得到了左右子树的高度之后,我们该用什么方法来判断它的高度差是不是符合要求的呢? 如果高度差小于1,我们返回当前二叉树的高度,否则返回-1,这个返回的-1表示它已经不是一个平衡二叉树了
该部分具体代码如下
int leftHeight = getHeight(node->left); // 左
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right); // 右
if (rightHeight == -1) return -1;
int result;
if (abs(leftHeight - rightHeight) > 1) { // 中
result = -1;
} else {
result = 1 + max(leftHeight, rightHeight); // 以当前节点为根节点的树的最大高度
}
return result;
完整代码如下:
/**
* 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 getdepth(TreeNode* node){
if(node==nullptr){
return 0;
}
int leftdep=getdepth(node->left);
if(leftdep==-1) return -1;
int rightdep=getdepth(node->right);
if(rightdep==-1) return -1;
if(abs(leftdep-rightdep)>1) return -1;
else return max(leftdep,rightdep)+1;
}
bool isBalanced(TreeNode* root) {
if(root==nullptr) return true;
return getdepth(root)==-1? false :true;
}
};
257. 二叉树的所有路径
本题是让我们求二叉树的所有路径,比如一棵这样的二叉树
那么他的路径就是“1->2->5” 和 "1->3"
想要获得其路径,我们就要遍历二叉树,我们考虑使用递归的方式来遍历这棵二叉树,那么我们应该如何写递归函数呢?
先上递归三部曲:
1.确定函数参数和返回值:
因为我们要返回的是vector<string>类型,我认为将函数返回值设置为void比较方便,这样我们在函数参数中定义一个vector<string>的参数:result就可以方便地增删其内部的元素了
2.确定递归函数的终止条件
一般情况下,我们都是这么写函数的终止条件的:
if(node==NULL) return;
但是我不建议在这里使用这样的写法,为什么?因为当我们找到叶子节点时,就可以结束并且处理结果逻辑了,将路径添加到result中
什么是叶子节点?该节点不为空,并且其左右孩子都为空时就是我们要找到的叶子节点
所以我们在这里应该这样写递归的终止条件:
if(node->left==NULL&&node->right==NULL){
处理逻辑
}
最终处理的逻辑如下:
if (node->left == NULL && node->right == NULL) { // 遇到叶子节点
string sPath;
for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点)
result.push_back(sPath); // 收集一个路径
return;
}
3.确定单层递归的逻辑:
我们先得明确是用什么样的遍历方式,才能写好递归逻辑,这题使用的是前序遍历,那么我们为什么要使用前序遍历呢?因为只用这样,我们才能按照“由父到子”的节点顺序,输出二叉树的一条路径,那么有小伙伴说了,使用后序遍历,然后reverse一下result不行吗?我的回答是,如果使用reverse函数,那么箭头->又该怎么办呢?
确定了要使用前序遍历,我们再来讲讲本题要用到的另一个知识点:回溯算法
为什么这里会用到回溯算法呢?
让我们先来简单介绍一下回溯算法,通俗的来讲,回溯算法就是一条路走到头,一步步得回退,直到回退到原位置(在这一步步的回退中,记录下每一次的回退内容),再重新开始走新的一条路。
我们还是拿出这棵二叉树来举例
回溯算法的实例模拟过程:先从头结点“1”开始走,一边走一边记录遇到的节点,直到叶子节点“5”时,我们就拿到了一条完整的路径了“1->2->5”,此时我们开始回退,将记录下来的5弹出,再回退,将2弹出,再回退到头结点时,又开始新的一条路径,一直走,遇到叶子节点“3”时停止,得到一条完整路径“1->3”弹出3,再回退到头结点1。
看到了回溯算法的简单介绍之后,相信大家也明白了,为什么这里要用到回溯算法,因为只有这样我们才能重复使用到“1”这个节点——左右子树的交点,正确得返回其完整路径。
我们又应该如何写回溯的代码呢?我们要牢记一点:回溯和递归是一一对应的,有一个递归,就要有一个回溯
所以回溯部分代码如下:
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) {
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
很多同学看到了回溯弹出的操作之后,又开始晕了,不理解递归是如何进行了。在本题中,我抽象的概括为:看到调用递归的逻辑,则说明它将直接走到二叉树的底部(即叶子节点),并且它的下一行代码不会立即执行,而是等到做完全部的递归时才会执行。
本题的完整代码如下:
/**
* 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:
void traversal(TreeNode* node,vector<int>&path,vector<string>&result){
path.push_back(node->val);
if(node->left==nullptr&&node->right==nullptr){
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(node->left){
traversal(node->left,path,result);
path.pop_back();
}
if(node->right){
traversal(node->right,path,result);
path.pop_back();
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string>result;
vector<int>path;
traversal(root,path,result);
return result;
}
};
404. 左叶子之和
我们要求左子叶之和,我们就要遍历二叉树,我采用递归遍历的方式:
先上递归三部曲:
1.确定函数参数和返回值:
返回值为int型,函数参数暂定为TreeNode*
2.确定递归函数的终止条件:
当root==NULL时返回0;
3.确定单层递归的逻辑:
先明确遍历方式为后序遍历(左右中)
那么我们应该如何确定遍历到的节点是否为左节点呢?
当该节点为左节点并且不为空,其下一个左节点为空,下一个右节点为空是,说明该节点为左子叶。所以函递归函数具体代码如下:
nt getleftnode(TreeNode* root){
if(root==nullptr){
return 0;
}
int leftnum=getleftnode(root->left);//左
if(root->left!=NULL&&root->left->left==NULL&&root->left->right==NULL)
leftnum=root->left->val;//确定其为左子叶
int rightnum=getleftnode(root->right);//右
int result=leftnum+rightnum;
return result;
}
整体代码如下:
/**
* 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 getleftnode(TreeNode* root){
if(root==nullptr){
return 0;
}
int leftnum=getleftnode(root->left);//左
if(root->left!=NULL&&root->left->left==NULL&&root->left->right==NULL)
leftnum=root->left->val;
int rightnum=getleftnode(root->right);//右
int result=leftnum+rightnum;
return result;
}
int sumOfLeftLeaves(TreeNode* root) {
if(root==NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0;
return getleftnode(root);
}
};
精简后代码如下:
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
int leftValue = 0;
if (root->left != NULL && root->left->left == NULL && root->left->right == NULL) {
leftValue = root->left->val;
}
return leftValue + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
}
};