LeetCode之路:543. Diameter of Binary Tree

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012814856/article/details/76212141

一、引言

这道题有点难度,以至于我琢磨了好久好久好久(此处 while、递归懵逼循环),才最终做出来 :(

不过做出来之后我的心情是这样的:

yellow excited

哈哈,既然这道题这么有趣,先让我们看看题目吧:

Given a binary tree, you need to compute the length of the diameter of the tree. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root.

Exmaple:
Given a binary tree
Binary Tree
Return 3, which is the length of the path [4, 2, 1, 3] or [5, 2, 1, 3].

Note:
The length of path between two nodes is represented by the number of edges between them.

题目信息还是挺丰富的,简单翻译下:

给定一个二叉树,你需要去计算它的直径。二叉树的直径就是任意两个节点间最长的路径长度。这条路径可以通过也可以不通过根节点。

注意:
两个节点间的路径长度是计算的两个节点间连线的个数(也就是边的个数)。

这道题需要好好审题,因为如果理解不透的话,后面的逻辑分析也就无法展开了。

其实最重要的就是,理解什么叫做二叉树的直径:

二叉树中,任意两个节点间最长的路径长度。

任意两个节点的意思也就是说,可以从这个叶子节点到另一个叶子节点,也可以通过根节点也可以不通过。

举个非常极端的例子来说:

longest path

如图,其中划红线的区域就是该二叉树的直径。可见,一个二叉树的直径可以说与根节点没有直接关系。

那么这道题应该如何分析呢?

二、我之所思:找圆心

其实说到了直径:

diameter

我们自然就要想到圆心。

只要找到了圆心,那么一条直径实际上已经就在我们的掌控之中了。

那么最长的直径,又对应着怎么样的圆心呢?

对于这个问题,我的思路给出了这样的答案:

我们要找的最长的直径的圆心,必然有着这么一个特点:其左子树的深度与右子树的深度之和必然为所有节点中最大的。

那么这里就涉及到了两个方面:

  1. 如何遍历所有节点:使用递归方法遍历所有节点,去计算其左子树深度和右子树深度之和,找到和最大的一个返回和计算即可

  2. 如何计算一个节点的深度:计算一个节点的深度,我们可以同样使用递归遍历,每层递归计算每一个节点左子树和右子树的深度之中,取其中最大的一个,并且要加 1(因为一层递归相当于就是递进一层深度)

其实这两个方面的问题解决了,这个问题也就解决了,也就是使用了两个递归,接下来看代码吧:

// my solution 6 , runtime = 26 ms
class Solution6 {
public:
    int diameterOfBinaryTree(TreeNode *root) {
        traverseBinaryTree(root);
        int diameter = 0;
        for (auto i : list) {
            int sumary = 0;
            sumary = depthOfNode(i->left) + depthOfNode(i->right);
            diameter = sumary > diameter ? sumary : diameter;
        }
        return diameter;
    }

    void traverseBinaryTree(TreeNode *node) {
        if (!node) return;
        list.push_back(node);
        traverseBinaryTree(node->left);
        traverseBinaryTree(node->right);
    }

    int depthOfNode(TreeNode *node) {
        if (!node) return 0;
        return max(depthOfNode(node->left), depthOfNode(node->right)) + 1;
    }

private:
    vector<TreeNode*> list;
};

尽管上面已经分析的很清楚了,对于这块代码我还是要讲一下的:

  1. 三个函数:这一块有三个函数,主要函数是 diameterOfBinaryTree() ,用来接受题目输入并且输出结果;在这个函数中调用 traverseBinaryTree() 函数来实现了二叉树的遍历,将二叉树中所有的节点都放置到一个存储容器 list 中,以便后面使用;最后遍历这个容器 list(也就是遍历所有的树节点),挨个计算每个节点的左子树深度和右子树深度之和,找到其中最大的一个返回

  2. 二叉树节点存储容器:这个容器被定义为 std::vector<TreeNode*> 类型,用来方便后面遍历求节点深度

  3. 递归求节点深度:求一个节点的深度,必然是求其左子树和右子树中深度较大的一个的深度 + 1;按照这个规律递归即可

可以说这个方法的逻辑还是非常清晰的,总结来就是一句话:

遍历二叉树的所有节点,计算以每个节点为圆心能得到的最长直径长度,返回其中最长的直径长度即可

怎么样,这道题的代码实现不是很难,难的是要怎么分析这个问题,找到 圆心 这个概念来分析问题才是这道题能够做出来的关键所在。

三、分析的胜利:最高票答案的妙处

好不容易做完了一道题,终于成功 AC,即使再喜悦也不要忘了看看最高票答案。

这里直接上代码吧,用代码说话:

// perfect solution , runtime = 26 ms
class Solution7 {
public:
    int diameterOfBinaryTree(TreeNode *root) {
        if (!root) return 0;
        int res = depthOfNode(root->left) + depthOfNode(root->right);
        return max(res, max(diameterOfBinaryTree(root->left), diameterOfBinaryTree(root->right)));
    }

    int depthOfNode(TreeNode *node) {
        if (!node) return 0;
        return max(depthOfNode(node->left), depthOfNode(node->right)) + 1;
    }
};

最直观的,最高票答案比我的少了一个函数,少了哪个函数呢,少了那个缓存树结构的函数。或许乍一看代码,还看不懂,没事,这里我贴出它的说明:

We can solve this problem with two different cases:
1. If the longest path will include the root node, then the longest path must be the depth(root->right) + depth(root->left)
2. If the longest path does not include the root node, this problem is divided into 2 sub-problem: set left chid and right child as the new root separately, and repeat step1.

这里作者的说明已经非常详细了,我简单翻译下:

这个问题可以分两个方面来考虑:
1. 最长路径经过根节点:那么根节点的左子树的深度和右子树的深度就是我们的结果
2. 最长路径没有经过根节点:这个问题就分为两个子问题,分别设置新的根节点为其左子节点和右子节点,然后重复步骤 1

妙!
绝妙!
真的是太妙了!

通过重复设置根节点,使用递归达到我们寻找最长路径的目的。

其中最关键的代码是这句:

return max(res, max(diameterOfBinaryTree(root->left), diameterOfBinaryTree(root->right)));

这句代码是什么意思呢?

返回最长路径值,在当前根节点的 左子节点的深度和右子节点的深度之和以左子节点为新的根节点的结果以右子节点为新的根节点的结果 中找到最大的一个返回,其中后两个用递归实现。

可以说,两个递归,一个移动根节点,一个计算深度,完美精巧的解决了这个看似复杂的问题。

这就是程序设计的魅力所在,这段代码美丽的无可复加 ^_^。

四、总结

这个问题已经卡了我很久了,上一篇有关 LeetCode 的博客已经是 7 月 6 号的事情了。在这段时间里,我看了些书,写了一些有趣的东西(XmlParser、LeetCodeDataStructure等等),当然也一直有在思考这道题。间断的思考,一直也没有思考出来,直到今天终于解决了。

开心!

真的很开心!

自己刷出来很开心,看到了最高票答案的精巧优雅更加开心!

作为程序员的快乐:

To be Stronger!

展开阅读全文

没有更多推荐了,返回首页