题目
标题和出处
标题:在二叉树中分配硬币
难度
6 级
题目描述
要求
给定一个有 n \texttt{n} n 个结点的二叉树的根结点 root \texttt{root} root,树中的每个结点 node \texttt{node} node 上有 node.val \texttt{node.val} node.val 枚硬币。整个树中总共有 n \texttt{n} n 枚硬币。
在一次移动中,我们可以选择两个相邻的结点,然后将一枚硬币从其中一个结点移动到另一个结点。可以从父结点移动到子结点,或者从子结点移动到父结点。
返回使每个结点上恰好有一枚硬币所需的最少移动次数。
示例
示例 1:
输入:
root
=
[3,0,0]
\texttt{root = [3,0,0]}
root = [3,0,0]
输出:
2
\texttt{2}
2
解释:从树的根结点开始,我们将一枚硬币移到它的左子结点上,一枚硬币移到它的右子结点上。
示例 2:
输入:
root
=
[0,3,0]
\texttt{root = [0,3,0]}
root = [0,3,0]
输出:
3
\texttt{3}
3
解释:从根结点的左子结点开始,我们将两枚硬币移到根结点上(移动两次)。然后,我们把一枚硬币从根结点移到右子结点上。
数据范围
- 树中结点数目是 n \texttt{n} n
- 1 ≤ n ≤ 100 \texttt{1} \le \texttt{n} \le \texttt{100} 1≤n≤100
- 0 ≤ Node.val ≤ n \texttt{0} \le \texttt{Node.val} \le \texttt{n} 0≤Node.val≤n
- 所有的 Node.val \texttt{Node.val} Node.val 之和是 n \texttt{n} n
解法
思路和算法
为了计算平均分配硬币的最少移动次数,需要计算每个子树中的硬币数与结点数的差值。差值等于 0 0 0 表示子树中的硬币树等于结点数;差值大于 0 0 0 表示子树中的硬币数大于结点数,需要将多余的硬币移动到当前子树根结点的父结点;差值小于 0 0 0 表示子树中的硬币数小于结点数,需要将缺少的硬币从当前子树根结点的父结点移动到当前子树。在计算每个子树中的硬币数与结点数的差值时,同步计算移动次数。
如果二叉树为空,则差值为 0 0 0。如果二叉树不为空,则差值为的左子树的差值、右子树的差值与根结点的差值之和,其中左子树的差值与右子树的差值是规模更小的问题,可以使用同样的方法计算,根结点的差值等于根结点值减 1 1 1,即根结点的硬币数与目标硬币数(目标硬币数为 1 1 1)之差。得到左子树的差值与右子树的差值之后即可更新移动次数。对于每个子树,如果差值为 x x x,则当 x > 0 x > 0 x>0 时需要将 ∣ x ∣ |x| ∣x∣ 个硬币从该子树移动到根结点,当 x < 0 x < 0 x<0 时需要将 ∣ x ∣ |x| ∣x∣ 个硬币从根结点移动到该子树,因此需要 ∣ x ∣ |x| ∣x∣ 次移动。
计算二叉树的差值可以使用深度优先搜索实现。整个过程是一个递归的过程,递归的终止条件是当前子树为空,此时差值为 0 0 0。其余情况下,首先计算左子树和右子树的差值,然后计算当前子树的差值并更新移动次数。
遍历结束之后即可得到平均分配硬币的最少移动次数。由于上述计算过程中,移动次数等于需要移动的硬币数,因此可以确保得到的移动次数是最少的。
代码
class Solution {
int moves = 0;
public int distributeCoins(TreeNode root) {
count(root);
return moves;
}
public int count(TreeNode node) {
if (node == null) {
return 0;
}
int countLeft = count(node.left);
int countRight = count(node.right);
moves += Math.abs(countLeft) + Math.abs(countRight);
return countLeft + countRight + node.val - 1;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。每个结点都被访问一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。空间复杂度主要是递归调用的栈空间,取决于二叉树的高度,最坏情况下是 O ( n ) O(n) O(n)。