美文网首页
2020-02-25 刷题 2(二叉树)

2020-02-25 刷题 2(二叉树)

作者: nowherespyfly | 来源:发表于2020-02-25 23:49 被阅读0次

104 二叉树的最大深度

比较容易写的是递归做法:

time: 92.98%, memory: 5.14%
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        int l = maxDepth(root -> left), r = maxDepth(root -> right);
        int d = l > r ? l : r;
        return d + 1;
    }
};

迭代做法:
迭代做法相对麻烦一点,需要设置一个栈,栈的每个元素由树节点和深度构成。对树进行先序遍历,每次迭代出栈当前栈顶节点,同时用该节点的深度更新最大深度,然后依次入栈右孩子和左孩子。

time: 72.84%, memory: 5.14% 
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    struct DPair{
        TreeNode *node;
        int d;
        DPair(int dep, TreeNode *n): d(dep), node(n) {}
    };
    int maxDepth(TreeNode* root) {
        if(!root) return 0;
        stack<DPair> s;
        TreeNode *c = root;
        int max_dep = 0;
        s.push(DPair(1, c));
        DPair p = DPair(0, NULL);
        while(!s.empty()){
            p = s.top();
            s.pop();
            if(p.node -> right)
                s.push(DPair(p.d + 1, p.node -> right));
            if(p.node -> left)
                s.push(DPair(p.d + 1, p.node -> left));
            if(p.d > max_dep) max_dep = p.d;
        }
        return max_dep;
    }
};

98 验证二叉搜索树

标签:二叉树遍历,中序遍历

不愧是中等题,提交了好多次。中等题目教我做人。

误区:开始以为简单递归一下就行,但是这样只能保证直接后继满足搜索关系,不能保证整棵树满足搜索关系。所以需要提供一个额外的遍历函数。

  • 后序遍历
    开始采用了后序遍历的做法,这种做法是通过返回更多数值来实现的。设计一种结构体Ret,每次迭代返回当前子树的最小值和最大值,用来跟父节点进行比较,但是这样写出来代码比较长,而且个人也觉得这种解法不漂亮。
24ms, 24.8M
/**
* Definition for a binary tree node.
* struct TreeNode {
*     int val;
*     TreeNode *left;
*     TreeNode *right;
*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
   struct Ret{
       bool is_valid;
       int min_ele, max_ele;
       Ret(bool v, int min, int max): is_valid(v), min_ele(min), max_ele(max) {}
   };
   Ret ValidCheck(TreeNode *root){
       if(!root) return Ret(true, INT_MIN, INT_MAX);
       Ret l(true, root -> val, root -> val), r(true, root -> val, root -> val);
       
       if(root -> left){
           l = ValidCheck(root -> left);
       }
       if(root -> right){
           r = ValidCheck(root -> right);
       }
       if(!l.is_valid || !r.is_valid) 
           return Ret(false, 0, 0);
       if(root -> left && l.max_ele >= root -> val) 
           return Ret(false, 0, 0);
       if(root -> right && r.min_ele <= root -> val)
           return Ret(false, 0, 0);
       return Ret(true, l.min_ele, r.max_ele);
   }
   bool isValidBST(TreeNode* root) {
       return ValidCheck(root).is_valid;
   }
};
  • 先序遍历
    后来改用了先序遍历的方法,通过提供更多的参数来实现。每次提供从根节点到当前节点的子树的上界和下界,用来跟当前节点比较。这种做法比较恶心的地方是,因为最开始需要提供平凡的一个最小值和最大值,当当前节点的值是INT_MAX或者INT_MIN时,就会判断错误。后来采用了整数指针,一开始为空,后面再赋值,就可以排除因为节点的极值导致判断错误的问题。
    代码:
20ms, 24.2M
/*
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    
    bool ValidCheck(TreeNode *root, int *lower, int *upper){
        if(lower && root -> val <= *lower) return false;
        if(upper && root -> val >= *upper) return false;
        bool l = true, r = true;
        if(root -> left)
            l = ValidCheck(root -> left, lower, &(root -> val));
        if(root -> right)
            r = ValidCheck(root -> right, &(root -> val), upper);
        return l && r;
    }
    bool isValidBST(TreeNode* root) {
        if(!root) return true;
        return ValidCheck(root, NULL, NULL);
    }
};

比较上面两种做法,第一种是自底向上的思想,用左子树和右子树分别提供当前节点的上下界;第二种是自顶向下的思想,用从根节点到当前节点的路径上的节点来提供上下界。

  • 中序遍历
    中序遍历真的是中非常非常巧妙的做法,仔细想一下,就会发现,中序遍历的顺序恰好是树的搜索顺序,也就是说,如果一棵树满足搜索顺序,那么,中序遍历的过程中,后面的元素一定比前面大。
    换一种方法解释,将当前树分为左子树,父节点,右子树三部分,我们需要的就是,左子树的值都比父节点小,右子树的值都比父节点大,这恰好就是中序遍历的顺序。真的很巧妙,我壶了。
32ms, 23.9M
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        if(!root) return true;
        int *cur_max = NULL;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode* t = root -> left;
        while(!s.empty() || t){
            while(t){
                s.push(t);
                t = t -> left;
            }
            t = s.top();
            s.pop();
            if(cur_max && ((t -> val) <= *cur_max))
                return false;
            cur_max = &(t -> val);
            t = t -> right;
        }
        return true;
    }
};

总结:先序遍历是一种自顶向下的遍历方法,父节点先于两个子树,后序遍历则是自底向上,先子树后父节点,而中序遍历则是二分的想法,通过父节点将两棵子树区分开。
这道题是一个很好的题目,不仅可以考虑多种遍历方法来解,同时更启发我们去思考,每种遍历方法的本质是什么,对于一个问题来说,更适合的遍历方法是什么。


相关文章

  • 2020-02-25 刷题 2(二叉树)

    104 二叉树的最大深度 比较容易写的是递归做法: 迭代做法:迭代做法相对麻烦一点,需要设置一个栈,栈的每个元素由...

  • 一次分糖果引发的优化案例

    刷题刷题刷题,二叉树,链表,LIS,真是乐趣无穷。今天在做老师准备的题目,公平分糖果的问题,看起来题目简单好懂,万...

  • leecode刷题(24)-- 翻转二叉树

    leecode刷题(24)-- 翻转二叉树 翻转二叉树 翻转一棵二叉树。 示例: 输入: 输出: 备注:这个问题是...

  • leecode刷题(30)-- 二叉树的后序遍历

    leecode刷题(30)-- 二叉树的后序遍历 二叉树的后序遍历 给定一个二叉树,返回它的 后序 遍历。 示例:...

  • leecode刷题(28)-- 二叉树的前序遍历

    leecode刷题(28)-- 二叉树的前序遍历 二叉树的前序遍历 给定一个二叉树,返回它的 前序 遍历。 示例:...

  • leecode刷题(29)-- 二叉树的中序遍历

    leecode刷题(29)-- 二叉树的中序遍历 二叉树的中序遍历 给定一个二叉树,返回它的中序 遍历。 示例: ...

  • leecode刷题(25)-- 二叉树的层序遍历

    leecode刷题(25)-- 二叉树的层序遍历 二叉树的层序遍历 给定一个二叉树,返回其按层次遍历的节点值。 (...

  • 刷leetCode算法题+解析(九)

    因为最近的拖拉,生生把计划每天的刷题改成了两天一刷,所以今天多做几道吧。 二叉树最大深度 给定一个二叉树,找出其最...

  • leetcode337

    隔行二叉树问题1.动态规划问题还是应该写一下公式。2.这个题对我的难点之一在于,我固化的思维,完全想不到,刷题的方...

  • 树的子结构

    《剑指offer》刷题笔记。如有更好解法,欢迎留言。 关键字:二叉树 递归 题目描述: 输入两棵二叉树A,B,判断...

网友评论

      本文标题:2020-02-25 刷题 2(二叉树)

      本文链接:https://www.haomeiwen.com/subject/tfkfchtx.html