979. 在二叉树中分配硬币

题目描述:

给你一个有 n 个结点的二叉树的根结点 root ,其中树中每个结点 node 都对应有 node.val 枚硬币。整棵树上一共有 n 枚硬币。

在一次移动中,我们可以选择两个相邻的结点,然后将一枚硬币从其中一个结点移动到另一个结点。移动可以是从父结点到子结点,或者从子结点移动到父结点。

返回使每个结点上 只有 一枚硬币所需的 最少 移动次数。

示例 1:

输入:root = [3,0,0]
输出:2
解释:一枚硬币从根结点移动到左子结点,一枚硬币从根结点移动到右子结点。

 示例 2:

输入:root = [0,3,0]
输出:3
解释:将两枚硬币从根结点的左子结点移动到根结点(两次移动)。然后,将一枚硬币从根结点移动到右子结点。

提示:

  • 树中节点的数目为 n
  • 1 <= n <= 100
  • 0 <= Node.val <= n
  • 所有 Node.val 的值之和是 n

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/distribute-coins-in-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解:深度优先搜索

使每个结点上 只有 一枚硬币所需的移动次数,就等价于所有路径上硬币移动的次数之和

对于图中的红色路径,我们来讨论与它有关的硬币移动情况:

 如果我们隐藏以A为根节点的子树的内部硬币移动的细节,将其看为一个整体,那么该整体从红色路径流入或流出的硬币数的值等于其整个子树的结点数减去其硬币数的绝对值。

如何求得这个值呢?

因为我们遍历时的对象为每个结点而非路径,所以我们可以通过间接的方法求得:

这个 子树A的结点数=A左子树的结点数+A右子树的结点数+1

这个 子树A的硬币数=A左子树的硬币数+A右子树的硬币数+A结点的硬币数

两个数相减的绝对值的代码表示为:

abs(left_counts+right_counts+1-right_sum-left_sum-node->val)

根节点和叶子结点会有影响吗?

对于根节点,其对应的树就是整个树,硬币数与结点数相等,值为0,不影响。

对于叶子结点,其没有子树,它对应的流通量就是 abs(node->val-1),不影响。

综上所述,我们可以用深度优先遍历的方式遍历整棵树,然后每次返回节点左右子树的结点数和硬币数,每次遍历增加移动值sum

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
    int sum = 0;
    pair <int ,int > dfs(TreeNode* node){
        if(node == nullptr) return {0,0};  //结点为空,返回0
        auto [left_counts,left_sum]=dfs(node->left);  //遍历左子树
        auto [right_counts,right_sum]=dfs(node->right);  //遍历右子树
        sum+=abs(left_counts+right_counts+1-right_sum-left_sum-node->val);  //移动值
        return {left_counts+right_counts+1,left_sum+right_sum+node->val};  //返回当前子树的结点总数和硬币总数
    }

public:
    int distributeCoins(TreeNode* root) {
        dfs(root);  //从根节点开始遍历
        return sum;
    }
};

附:关于官方题解的思路:

 

对于每个结点A,其统计其三条路径对应的移动量。

若要使得左子树每个节点的数目均为 1,此时 A 需要从 left 拿走的为金币数目 dfs(left),此时 left 与 A 之间(路径2)需要移动 ∣dfs(left)∣ 次金币;
若要使得右子树每个节点的数目均为 1,此时 A 需要从 right 拿走的为金币数目 dfs(right),此时 right 与 A 之间(路径3)需要移动 ∣dfs(right)∣ 次金币;
此时A节点的金币总数目即为 move(left)+move(right)+val(node),由于 A 本身需要保留一个金币,此时 A 的根节点需要向它拿走的金币数目(路径1)即为 move(left)+move(right)+val(node)-1;

class Solution {
public:    
    int distributeCoins(TreeNode* root) {
        int move = 0;

        function<int(const TreeNode *)> dfs = [&](const TreeNode *root) -> int {
            int moveleft = 0;
            int moveright = 0;
            if (root == nullptr) {
                return 0;
            }
            if (root->left) {
                moveleft = dfs(root->left);
            }        
            if (root->right) {
                moveright = dfs(root->right);
            }
            move += abs(moveleft) + abs(moveright);
            return moveleft + moveright + root->val - 1;
        };

        dfs(root);
        return move;
    }
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/distribute-coins-in-binary-tree/solution/zai-er-cha-shu-zhong-fen-pei-ying-bi-by-e4poq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值