110.平衡二叉树
一、做题感受&第一想法
自己用递归写出来啦!耶!夸夸
1.递归三要素的分析:
函数heightAndBalance()
,如果平衡返回树高,不平衡则返回不平衡的标记。
- 返回值和函数参数:如果平衡返回树高,不平衡则返回-1。参数传入子树根节点root
- 递归结束条件:root == NULL, 返回0。
- 单层递归的处理:如果左右子树高度差大于1,则不平衡,返回-1;否则,平衡,返回树高。
class Solution {
public:
bool isBalanced(TreeNode* root) {
if(heightAndBalance(root) == -1){
return false;
}
else{
return true;
}
}
int heightAndBalance(TreeNode* root){ //如果是平衡的,返回height;如果不平衡,返回-1。
if(root == NULL) return 0;
int l = heightAndBalance(root->left);
int r = heightAndBalance(root->right);
if(l == -1 || r == -1){
return -1; //说明当前树已经不平衡了
}
else if(l-r == -1 || l-r == 0 || l-r == 1){ //如果左右是平衡的
return max(l,r) + 1;
}
else return -1; //如果不平衡
}
};
二、学习文章后收获
1.节点的深度&高度
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
- 使用前序求的就是深度(从上往下,回溯),使用后序求的是高度(从下往上,递归)。
另:关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
2.迭代法(回溯,还未学,后面补)
- 能否层序?不能:层序遍历便于处理求深度问题,也就是自顶向下的逻辑。但是,判断树是否平衡是自底向上的逻辑,也就是求高度问题,所以层序遍历失效。
三、过程中遇到的问题
1.求绝对值:abs()
257. 二叉树的所有路径
一、做题感受&第一想法
没啥好想法。
二、学习文章后收获
1.回溯和递归
回溯和递归是一一对应的,有一个递归,就要有一个回溯
这种写法就不好,因为割裂的了回溯和递归:
if (cur->left) {
traversal(cur->left, path, result);
}
if (cur->right) {
traversal(cur->right, path, result);
}
path.pop_back();
应该这么写:
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) {
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
2.记录一下我写的冗长代码(仅记录,代码并不好)
- 割裂了回溯和递归
- int转string其实有
to_string()
函数,不需要自己实现
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if(root == nullptr) return result;
traversal(root,path,result);
return result;
}
void traversal(TreeNode* root, vector<int> &path, vector<string> &result){
path.push_back(root->val); //中
if(root->left == NULL && root->right == NULL){ //如果是叶子节点
string s;
for(int i = 0;i < path.size();i++){
s += change(path[i]);
// s += path[i];
if(i == path.size()-1) break;
s += "->";
}
result.push_back(s);
}
if(root->left){ //左
traversal(root->left, path, result);
}
if(root->right){ //右
traversal(root->right, path, result);
}
path.pop_back(); //回溯,从当前子树根出发能找到的所有路径已经全部被记录。
}
string change(int num){ //把int型数字转成string
string s;
int flag = 1;
if(num < 0){
flag = -1;
num = (-1) * num;
}
while(num){
s += (num % 10) + '0';
num /= 10;
}
reverse(s);
if(flag == -1){
s.insert(0,"-");
}
return s;
}
void reverse(string &s){ //双指针法翻转字符串
int i = 0, j = s.size()-1;
while(i<j){
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
i++;
j--;
}
return;
}
};
3.卡哥写的易于理解的版本
一个拓展:关于此处path能不能用string型
最好不要用string,因为用这个方法回溯的时候,是需要把最后数字去掉的。但是如果直接用string,删去最后一个数字就比较复杂,因为你不知道最后一个数是几位数、有没有负号。
所以,用vector存数字比较方便,这样可以一次性弹出数字。
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;
}
};
4.精简版本
精髓:传递参数path时是拷贝而不是引用,这样就不需要每次回溯时弹出元素来维护path。
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;
}
};
5.对递归的分析
- 返回值:空
- 函数参数:一个path记录当前路径,一个result记录返回结果,一个root指向当前子树
- 返回条件:遇到叶节点,则找到了一条路径,把path加入result。
- (遍历顺序:前序,因为路径是从根开始往下慢慢寻找,这样显然最佳。)
6.对不起,我好累,我不想看了,迭代法下次再看吧
三、过程中遇到的问题
1.C语言关于int型num转cahr[]的几个注意点
- num是正是负?答:负数加负号,数字转成绝对值,再继续处理
- num是否是多位数(不是仅仅一位数)?答:循环取余
- 针对一位数字(正数),如何转成char型?答:数字 + ‘0’
2.c++中int和string互相转换
- int转string:
std::to_string(int num)
函数 - string转int:
2.string型如何翻转?
- 没有
reverse()
函数 - 可以使用字符串翻转双指针法。
3.string的insert函数
- 没有插入单个字符的方法!但是可以插字符串,可以插n个字符,可以插string。
404.左叶子之和
一、做题感受&第一想法
我的方法:递归法(先序遍历)
一个小经验:先序遍历一般都是需要一个可以记录返回值的变量(成员变量、全局变量、引用型函数参数,均可),因为它是自顶向下来遍历的。
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int sum = 0;
if(root) sumLeft(root,sum);
return sum;
}
void sumLeft(TreeNode* root, int &sum){ //可以保持root一定不是NULL
// 中:不需要操作
if(root->left){ //左
if(root->left->left == NULL && root->left->right == NULL){ //①找到一个左叶子
sum += root->left->val;
}
else sumLeft(root->left,sum); //②不是左叶子,那么就继续处理左子树
}
if(root->right) sumLeft(root->right,sum); //右:继续处理右子树。
return; //中左右处理完,结束。
}
};
二、学习文章后收获
1.递归法(后序遍历)
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if(root == NULL) return 0;
int l = 0, r = 0;
//左
if(root->left != nullptr && root->left->left == nullptr && root->left->right ==nullptr){ //找到一个左叶子
l = root->left->val;
}
else if(root->left != nullptr) l = sumOfLeftLeaves(root->left); //如果不是左叶子
if(root->right != nullptr) r = sumOfLeftLeaves(root->right); //右
return l + r; //中
}
};
2.迭代法
某一种迭代法的遍历+判断是否是左叶子
①层序遍历
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
queue<TreeNode*> q;
TreeNode* cur = nullptr;
int sum = 0;
if(root) q.push(root);
while(!q.empty()){
cur = q.front();
q.pop();
if(cur->left != nullptr && cur->left->left == nullptr && cur->left->right == nullptr) //找到左叶子
{
sum += cur->left->val;
}
if(cur->left) q.push(cur->left);
if(cur->right) q.push(cur->right);
}
return sum;
}
};
②先序遍历
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = nullptr;
int sum = 0;
if(root) st.push(root);
while(!st.empty()){
cur = st.top();
st.pop();
if(cur->left != nullptr && cur->left->left == nullptr && cur->left->right == nullptr){ //找到一个左叶子
sum += cur->left->val;
}
if(cur->left) st.push(cur->left);
if(cur->right) st.push(cur->right);
}
return sum;
}
};