代码随想录算法训练营第17天|110.平衡二叉树、257. 二叉树的所有路径、404.左叶子之和

110.平衡二叉树

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

自己用递归写出来啦!耶!夸夸

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. 二叉树的所有路径

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

没啥好想法。

二、学习文章后收获

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.左叶子之和

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

我的方法:递归法(先序遍历)
一个小经验:先序遍历一般都是需要一个可以记录返回值的变量(成员变量、全局变量、引用型函数参数,均可),因为它是自顶向下来遍历的。

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;
    }
};

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值