代码随想录算法训练营第三期day17-二叉树04

目录

1、T110:平衡二叉树

法1、递归

法2、迭代

2、T257:二叉树的所有路径

法1、递归

Ⅰ、版本1【最容易理解】

Ⅱ、版本2【分为隐式和显式】

法2、迭代

3、T404:左叶子之和

法1、递归

法2、迭代(反正没有顺序要求,用队列或栈都行)

Ⅰ、队列层序遍历(本人的本能)

Ⅱ、栈迭代


1、T110:平衡二叉树

T110:给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

提示:

  • 树中的节点数在范围 [0, 5000] 内

  • -104 <= Node.val <= 104

S:

法1、递归

既然要求比较高度,理应是要用后序遍历

C++:

    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
    int getHeight(TreeNode* node) {
        if (!node) return 0;
        int leftHeigt = getHeight(node->left);
        if (leftHeigt == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;

        return abs(leftHeigt - rightHeight) > 1 ? -1 : 1 + max(leftHeigt, rightHeight);
        // 以当前节点为根节点的树的最大高度
    }

Java:

    public boolean isBalanced(TreeNode root) {
        return getHeight(root) == -1 ? false : true;
    }
    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 Math.abs(leftHeight - rightHeight) > 1 ? -1 : Math.max(leftHeight, rightHeight);
        // 🚩用上一行就错!因为有可能出现leftHeight和rightHeight均为-1的情况
        return Math.abs(leftHeight - rightHeight) > 1 ? -1 : 1 + Math.max(leftHeight, rightHeight); 
    }

法2、迭代

我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。

本题的迭代方式可以先定义一个函数,专门用来求高度。

这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)

C++:

    bool isBalanced(TreeNode* root) {
        if (!root) return true;
        stack<TreeNode*> st;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();

            if (abs(getHeight(node->left) - getHeight(node->right)) > 1) {
                return false;
            }
            if (node->right) st.push(node->right);
            if (node->left) st.push(node->left);
        }
        return true;
    }
    int getHeight(TreeNode* node) {
        if (!node) return 0;
        stack<TreeNode*> st;
        st.push(node);
        int res = 0;
        int height = 0;
        while (!st.empty()) {
            TreeNode* cur = st.top();
            if (cur) {
                st.pop();
                st.push(cur);
                st.push(nullptr);
                ++height;
                // st.push(cur->right);
                // st.push(cur->left);
                if (cur->right) st.push(cur->right);
                if (cur->left) st.push(cur->left);
            } else {
                st.pop();
                cur = st.top();
                st.pop();
                // res = res > height ? res : height;
                --height;//回溯
            }
            res = res > height ? res : height;
            // --height;
        }
        return res;
    }

此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。

虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。

2、T257:二叉树的所有路径

T257:给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

S:

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。

在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。

法1、递归

Ⅰ、版本1【最容易理解】

C++:

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if (!root) return res;
        vector<int> paths;
        traversal(root, paths, res);
        return res;
    }
    // void traversal(TreeNode* node, vector<int> paths, vector<string> res) {//别又犯病!
    void traversal(TreeNode* node, vector<int>& paths, vector<string>& res) {
        paths.push_back(node->val);
        if (!node->left && !node->right) {
            string path;
            for (int i = 0; i < paths.size() - 1; ++i) {
                path += to_string(paths[i]);
                path += "->";
            }
            path += to_string(paths[paths.size() - 1]);
            res.push_back(path);
            return;
        }
        if (node->left) {
            traversal(node->left, paths, res);
            paths.pop_back();// 回溯
        }
        if (node->right) {
            traversal(node->right, paths, res);
            paths.pop_back();// 回溯
        }
    }

Java:

    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new LinkedList<>();
        if (root == null) return res;
        List<Integer> paths = new LinkedList<>();
        traversal(root, paths, res);
        return res;
    }
    void traversal(TreeNode node, List<Integer> paths, List<String> res) {
        paths.add(node.val);
        if (node.left == null && node.right == null) {
            StringBuilder path = new StringBuilder();
            //最后一个不能再接箭头
            for (int i = 0; i < paths.size() - 1; ++i) {
                path.append(paths.get(i));
                path.append("->");
            }
            path.append(paths.get(paths.size() - 1));
            res.add(path.toString());
        }
        if (node.left != null) {
            traversal(node.left, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
        if (node.right != null) {
            traversal(node.right, paths, res);
            paths.remove(paths.size() - 1);// 回溯
        }
    }

Ⅱ、版本2【分为隐式和显式】

1、隐式回溯

C++:

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if (!root) return res;
        string path;
        traversal(root, path, res);
        return res;
    }

    // void traversal(TreeNode* node, string& path, vector<string>& res) {//🚩不要用引用,这很重要!!!
    void traversal(TreeNode* node, string path, vector<string>& res) {
        // path += node->val;
        path += to_string(node->val);
        if (!node->left && !node->right) {
            res.push_back(path);
            return;
        }
        if (node->left) {
            traversal(node->left, path + "->", res);
        }
        if (node->right) {
            traversal(node->right, path + "->", res);
        }
    }

代码精简了不少,也隐藏了不少东西。

在函数定义的时候 void traversal(TreeNode* cur, string path, vector<string>& result) ,定义的是string path,每次都是复制赋值(~值传递),不用使用引用,否则就无法做到回溯的效果

在上面的代码中,貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。

Java:

    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) return res;
        String path = "";
        traversal(root, path, res);
        return res;
    }
    void traversal(TreeNode node, String path, List<String> res) {
        path += node.val;
        if (node.left == null && node.right == null) {
            res.add(path);
            return;
        }
        if (node.left != null) {
            traversal(node.left, path + "->", res);//🚩不会改变外面的path!
        }
        if (node.right != null) {
            traversal(node.right, path + "->", res);
        }
    }

2、显式回溯

C++:

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if (!root) return res;
        string path;
        traversal(root, path, res);
        return res;
    }
    void traversal(TreeNode* node, string path, vector<string>& res) {
        path += to_string(node->val);
        if (!node->left && !node->right) {
            res.push_back(path);
            return;
        }
        if (node->left) {
            // traversal(node->left, path + "->", res);
            path += "->";
            traversal(node->left, path, res);
            path.pop_back();
            path.pop_back();
        }
        if (node->right) {
            // traversal(node->right, path + "->", res);
            path += "->";
            traversal(node->right, path, res);🚩里面+=不会改变外面的path!
            path.pop_back();
            path.pop_back();
        }

    }

Java:不能用StringBuilder(如下,反正本人是用失败了,毕竟数字不是一定只有一位的)

失败版:

    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) return res;
        StringBuilder path = new StringBuilder();
        traversal(root, path, res);
        return res;
    }
    void traversal(TreeNode node, StringBuilder path, List<String> res) {
        path.append(node.val);
        if (node.left == null && node.right == null) {
            res.add(path.toString());
            return;
        }
        if (node.left != null) {
            path.append("->");
            traversal(node.left, path, res);
            path.deleteCharAt(path.length() - 1);
            path.deleteCharAt(path.length() - 1);
            path.deleteCharAt(path.length() - 1);
            // path.deleteCharAt(path.length() - 1);
        }
        if (node.right != null) {
            path.append("->");
            traversal(node.right, path, res);
            path.deleteCharAt(path.length() - 1);
            path.deleteCharAt(path.length() - 1);
            path.deleteCharAt(path.length() - 1);
            // path.deleteCharAt(path.length() - 1);
        }
    }

通过版:

    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) return res;
        String path = "";
        traversal(root, path, res);
        return res;
    }
    void traversal(TreeNode node, String path, List<String> res) {
        path += node.val;
        if (node.left == null && node.right == null) {
            res.add(path);
            return;
        }
        if (node.left != null) {
            path += "->";
            traversal(node.left, path, res);
            path = path.substring(0, path.length() - 2);//该方法是左闭右开区间
        }
        if (node.right != null) {
            path += "->";
            traversal(node.right, path, res);
            path = path.substring(0, path.length() - 2);
        }
    }

法2、迭代

除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。

C++:

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> res;
        if (!root) return res;
        stack<TreeNode*> treeSt;
        stack<string> pathSt;
        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 && !node->right) {
                res.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 res;
    }

Java:

    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res = new ArrayList<>();
        if (root == null) return res;
        Deque<TreeNode> nodes = new LinkedList<>();
        nodes.offerFirst(root);
        Deque<String> paths = new LinkedList<>();
        paths.offerFirst(String.valueOf(root.val));
        // paths.offerFirst(root.val + "");
        
        while (!nodes.isEmpty()) {
            TreeNode node = nodes.pop();
            String path = paths.poll();
            if (node.left == null && node.right == null) {
                res.add(path);
            }
            if (node.right != null) {//右
                // path += "->";
                // path += node.right.val;
                // paths.push(path);
                nodes.push(node.right);
                paths.push(path + "->" + node.right.val);
            }
            if (node.left != null) {//左
                // path += "->";
                // path += node.left.val;//不知为何用这两行就不对
                // paths.push(path);
                nodes.push(node.left);
                paths.push(path + "->" + node.left.val);
            }
        }
        return res;
    }

如上所示,不知为何按照

3、T404:左叶子之和

T404:给定二叉树的根节点 root ,返回所有左叶子之和。

提示:

  • 节点数在 [1, 1000] 范围内

  • -1000 <= Node.val <= 1000

S:

首先要清楚左叶子的定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点

法1、递归

递归的遍历顺序为后序(左右中),因为要通过递归函数的返回值来累加求取左叶子数值之和。

遇到左叶子节点的时候,记录数值,然后递归求 左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

C++:

    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;
        int leftValue = sumOfLeftLeaves(root->left);// 左
        if (root->left && !root->left->left && !root->left->right) {// 左子树就是一个左叶子的情况
            leftValue = root->left->val;//可能不理解为什么要替掉,也可以用midValue(见Java版)
        }
        int rightValue = sumOfLeftLeaves(root->right);// 右
        return leftValue + rightValue;// 中
    }

//->【精简版】
    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;
        int leftValue = 0;
        if (root->left && !root->left->left && !root->left->right) {
            leftValue = root->left->val;
        }
        return leftValue + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
    }

Java:

    public int sumOfLeftLeaves(TreeNode root) {
        if (root == null) return 0;
        if (root.left == null && root.right == null) return 0;//有没有都行
        int midValue = 0, leftValue = 0, rightValue = 0;
        if (root.left != null && root.left.left == null && root.left.right == null) {
            midValue = root.left.val;
        }
        if (root.left != null) {
            leftValue = sumOfLeftLeaves(root.left);
        }
        if (root.right != null) {
            rightValue = sumOfLeftLeaves(root.right);
        }
        return midValue + leftValue + rightValue;
    }

法2、迭代(反正没有顺序要求,用队列或栈都行)

Ⅰ、队列层序遍历(本人的本能)

C++:

    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;
        queue<TreeNode*> que;
        que.push(root);
        int sum = 0;
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; ++i) {
                TreeNode* node = que.front();
                que.pop();
                if (node->left) {
                    que.push(node->left);
                    if (!node->left->left && !node->left->right) {//必须!
                        sum += node->left->val;
                    }
                }
                if (node->right) que.push(node->right);
            }
        }
        return sum;
    }

Ⅱ、栈迭代

C++:

    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;
        stack<TreeNode*> st;
        st.push(root);
        int sum = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            if (node->left && !node->left->left && !node->left->right) {
                sum += node->left->val;
            }
            if (node->left) st.push(node->left);//如果按照后序,这两行应该上下对换(不过本题没关系)
            if (node->right) st.push(node->right);
        }
        return sum;
    }

迭代都不难,懒得写Java版了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值