文章目录
- 110.平衡二叉树
- 思路
- 伪代码
- CPP代码
- 257.二叉树的所有路径
- 思路
- 伪代码实现
- CPP代码
- 404.左叶子之和
- 思路
- 伪代码
- CPP代码
110.平衡二叉树
力扣题目链接
文章讲解:110.平衡二叉树
视频讲解:后序遍历求高度,高度判断是否平衡 | LeetCode:110.平衡二叉树
状态:第一眼要用后序遍历,因为对于某结点我们需要收集其左右孩子的信息。
思路
本题中,平衡二叉树的定义就是:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
并且既然是要求比较高度,必然要使用后序遍历。
这里的递归函数,通过返回二叉树的高度是最好的,即使我们不需要求二叉树的高度。因为在计算高度的过程中,我们才好去判断左右子树的高度差。
所以一定要注意的就是我们的递归函数代码本质上是求二叉树的高度,而不是直接去判断是否是平衡二叉树。
还有一个一定要注意的情况:
该二叉树不是平衡二叉树!因为左结点的2,其左孩子高度为2,右孩子高度为0!所以不是平衡二叉树,在代码中一定要考虑这种情况!
伪代码
-
确定递归函数的参数和返回值:
参数:当前结点;返回值:应当是以当前结点为根结点的树的高度。
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
int getHeight(TreeNode* node)
- 明确终止条件:递归的过程中依然是遇到空结点了为终止,返回0,表示当前结点为根结点的树的高度为0
if (node == NULL)
return 0
- 确定单层递归逻辑:
如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。
分别求出其左右子树的高度,然后如果差值小于等于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 + max(leftHeight, rightHeight);
else
return -1;
return result;
代码if (leftHeight == -1) return -1
这行代码一定要写上,因为他们也同样是在求高度,如果该根结点左子树的高度已经返回了-1,那么这颗树肯定不是平衡二叉树!
- 单层递归逻辑精简后的代码:
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
CPP代码
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
int getHeight(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
257.二叉树的所有路径
力扣题目链接
文章讲解:257.二叉树的所有路径
视频讲解:递归中带着回溯,你感受到了没?| LeetCode:257. 二叉树的所有路径
状态:只知道用前序遍历,剩下的代码特别是关于单层递归逻辑那里根本就写不出来,如何处理遇到分叉路口的情况呢?(即回溯逻辑) 还有一个要注意的点就是关于递归终止条件,本题的终止条件和之前的有什么不一样呢?
思路
本题其实就是求二叉树的遍历路径,那么我们就一定要保存从根结点开始遍历的路径。
很明显,要使用前序遍历。并且在本题中要有回溯的逻辑。
伪代码实现
- 确定递归函数的参数和返回值:我们传入的参数就包含了要记录结果的数组,所以返回值肯定是void,至于参数包括传入根节点,记录每一条路径的path,和存放结果集的result。
void traversal(TreeNode* node, vector<string>& result, vector<int>& path){
}
- 确定递归终止条件:之前的题目都是遍历到空结点即可终止递归;但是在本题中,我们是要寻找到叶子结点(防止左叶子为空,右叶子不为空的情况)才能终止。因为我们本质上要确定是叶子结点的时候,才能真正的结束。而且由于我们对最后遍历的叶子结点要进行操作,所以返回的地方一定要是该叶子结点,而不能是
node == NULL
的时候。
if (node == NULL) return;//No!
if (node->left == NULL && node->right == NULL) {
处理逻辑
}
为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。
再来看一下终止处理的逻辑:这里使用vector
结构path
来记录路径,所以要把vector
结构的path
转为string
格式,再把这个string
放进 result
里。
那么为什么使用了vector 结构来记录路径呢?
因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。
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;
}
-
确定单层递归逻辑:上面说过没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。
- 上面的代码写到遍历到叶子结点我们就终止循环,那么什么时候把叶子结点压入path中呢?写到终止条件的前一步即可
- 单层递归逻辑里面最难的其实就是如何进行回溯?其实就是对**
path
中的结点进行删除,然后加入新结点**。所以在代码中回溯和递归一定要一一对应,有一个递归就要有一个回溯。所以下面的代码逻辑是错的:
if (cur->left) { traversal(cur->left, path, result); } if (cur->right) { traversal(cur->right, path, result); } path.pop_back(); //这么写相当于把递归和回溯拆开了,一个在花括号里,一个在花括号外。 //正确写法 path.push_back(cur->val); //中 if (cur->left) { //左 traversal(cur->left, path, result); path.pop_back(); // 回溯 } if (cur->right) { //右 traversal(cur->right, path, result); path.pop_back(); // 回溯 }
CPP代码
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
// 这才到了叶子节点
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(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
精简版代码:
//精简版代码隐藏了回溯过程
class Solution {
private:
void traversal(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) traversal(cur->left, path + "->", result); // 左
if (cur->right) traversal(cur->right, path + "->", result); // 右
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
string path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
注意在函数定义的时候void traversal(TreeNode* cur, string path, vector<string>& result)
,定义的是string path
,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。(这里涉及到C++语法知识)
那么在如上代码中,貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在traversal(cur->left, path + "->", result);
中的 path + "->"
。 每次函数调用完,path
依然是没有加上"->" 的,这就是回溯了。
404.左叶子之和
力扣题目链接
文章讲解:404.左叶子之和
视频讲解:二叉树的题目中,总有一些规则让你找不到北 | LeetCode:404.左叶子之和
状态:还是有思路,我们的cur遍历到叶子结点的前一个结点,也就是cur结点的左结点的(左结点和右结点)为空,这样我们就能顺利找到左叶子。
思路
思路主要就是状态栏中的思路,主要是注意代码细节,代码实现个人感觉还是比较有难度的。
伪代码
-
确定递归的返回值和参数:判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int
使用题目中给出的函数就可以了。
int getSum(TreeNode* root){ }
-
确定递归的终止条件:遍历到空结点,左叶子值为0;当前遍历到叶子结点,左叶子值仍然为0
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return 0;
- 确定单层递归逻辑:
当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。
int leftValue = sumOfLeftLeaves(root->left); // 左
if (root->left && !root->left->left && !root->left->right) {
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
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 && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
}
};
//精简后代码如下
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);
}
};