一、引言
做这道题一定不能忘的一点:
tilt 是倾斜度的意思
(T_T鄙人英语战五渣,LeetCode 读题靠百度翻译,有本事来咬我啊)
好了,进入正题,这是一道非常有趣的题目,让我们来看看题目信息:
Given a binary tree, return the tilt of the whole tree.
The tilt of a tree node is defined as the absolute difference between the sum of all left subtree node values and the sum of all right subtree node values. Null node has tilt 0.
The tilt of the whole tree is defined as the sum of all nodes’ tilt.
Note:
1.The sum of node values in any subtree won’t exceed the range of 32-bit integer.
2.All the tilt values won’t exceed the range of 32-bit integer.
简要翻译下这道题:
给定一个二叉树,要求返回整个二叉树的倾斜度
一个树结点的倾斜度是:其左子树的所有结点的值之和与其右子树的所有结点的值之和的差的绝对值
一个二叉树的倾斜度是其所有树结点的倾斜度之和
这里需要注意的是,一个树结点的倾斜度,与其上的结点的 值 有关;而与之不同的,一个二叉树的倾斜度则与各个结点的 倾斜度 有关。
这里有两个概念:
前者需要拿着左子树和右子树的所有结点的和进行计算
后者需要拿着所有结点的倾斜度进行计算
并且这两者都需要进行指定结点往下广度遍历
分析到这里,我们应该对题意有了一个大概的认识,那么接下来让我们看看,如何完成这道题。
二、年年岁岁题相似,岁岁年年用递归
这道题使用递归来思考的话,逻辑会非常清晰。使用递归可以让我们脱离具体语言的矫揉造作,让我们直面问题的处理逻辑,可以提高我们解决问题的效率。
那么这道题为什么可以使用递归呢?
要知道选择什么方法,就必须要先分析问题,那么我们来分析下这个问题:
引言里已经分析出来了两个任务,一个是计算树结点的某一子树的所有结点值的和,另一个则是遍历整个二叉树,进行各个结点的倾斜的和运算
那么这里,我们从这两个分解任务入手,首先是计算结点值的和,那么计算结点值的和,是不是可以使用递归呢?我们拿到一个结点,然后判断该结点是否为空,是的话就返回 0 ;否则的话就将 该结点的值 与 该结点的左子树的所有结点值的和 和 该结点的右子树的所有结点的和 相加返回
其次,我们再来看看第二个任务,计算各个结点的倾斜度的和,这里与上一个任务的思路一样,只不过相加的是倾斜度而已:我们拿到一个结点,判断是否为空,是的话就返回 0 ;否则的话就将 该结点的倾斜度 与 左子树所有结点的倾斜度之和 和 右子树所有结点的倾斜度之和 相加返回
上述的解释中,第 2 点和 第 3 点可能有点描述得有点生硬,不过没关系,有时候代码比文字更加的生动形象:
// my solution 1 use recursion , runtime = 23 ms
class Solution {
public:
int countChild(TreeNode* node) {
if (node == nullptr) return 0;
return node->val + countChild(node->left) + countChild(node->right);
}
int findTilt(TreeNode* root) {
if (root == nullptr) return 0;
return abs(countChild(root->left) - countChild(root->right)) + findTilt(root->left) + findTilt(root->right);
}
};
代码逻辑非常清晰,如果有不懂的同学,可以反复看看代码之前那 3 点解析,已经说得非常清楚了。
三、更优化的追求:还是要保持可读性
这时候,有必要看看最高票答案是什么样子的。
class Solution {
public:
int findTilt(TreeNode* root) {
if(root == NULL) return 0;
int res = 0;
postorder(root, res);
return res;
}
private:
int postorder(TreeNode* root, int& res){
if(root == NULL) return 0;
int leftsum= postorder(root->left,res);
int rightsum = postorder(root->right,res);
res += abs(leftsum - rightsum);
return leftsum + rightsum + root->val;
}
};
这段代码我就不进行解析了,因为我觉得这段代码不如我的代码简洁和优美,大体的思路却都是一样的。
当然了,有些人或许会说,其实你的四行代码都还可以缩减为两行代码,比如:
class Solution {
public:
int findTilt(TreeNode* root) {
return root?abs(sum(root->left)-sum(root->right))+findTilt(root->left)+findTilt(root->right):0;
}
int sum(TreeNode* root) {
return root?root->val+sum(root->left)+sum(root->right):0;
}
};
这也是一位仁兄的答案,其实就是我的方法中,糅合了两个条件语句,减少了两行代码量。但是这样我觉得代码已经降低了不少可读性。
怎么说呢,我还是觉得,代码量尽量少的、逻辑清晰的代码才是最简洁和优美的代码。
四、总结
真的是:
年年岁岁题相似,岁岁年年用递归
这道题再次展现了递归方法的魅力。简化代码、精炼逻辑,真的是非常非常优美!
I Love Recursion!