leetcode
leetcode 2451 ~ 2500
收集所有金币可获得的最大积分

收集所有金币可获得的最大积分

难度:

标签:

题目描述

There exists an undirected tree rooted at node 0 with n nodes labeled from 0 to n - 1. You are given a 2D integer array edges of length n - 1, where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree. You are also given a 0-indexed array coins of size n where coins[i] indicates the number of coins in the vertex i, and an integer k.

Starting from the root, you have to collect all the coins such that the coins at a node can only be collected if the coins of its ancestors have been already collected.

Coins at nodei can be collected in one of the following ways:

  • Collect all the coins, but you will get coins[i] - k points. If coins[i] - k is negative then you will lose abs(coins[i] - k) points.
  • Collect all the coins, but you will get floor(coins[i] / 2) points. If this way is used, then for all the nodej present in the subtree of nodei, coins[j] will get reduced to floor(coins[j] / 2).

Return the maximum points you can get after collecting the coins from all the tree nodes.

 

Example 1:

Input: edges = [[0,1],[1,2],[2,3]], coins = [10,10,3,3], k = 5
Output: 11                        
Explanation: 
Collect all the coins from node 0 using the first way. Total points = 10 - 5 = 5.
Collect all the coins from node 1 using the first way. Total points = 5 + (10 - 5) = 10.
Collect all the coins from node 2 using the second way so coins left at node 3 will be floor(3 / 2) = 1. Total points = 10 + floor(3 / 2) = 11.
Collect all the coins from node 3 using the second way. Total points = 11 + floor(1 / 2) = 11.
It can be shown that the maximum points we can get after collecting coins from all the nodes is 11. 

Example 2:

Input: edges = [[0,1],[0,2]], coins = [8,4,4], k = 0
Output: 16
Explanation: 
Coins will be collected from all the nodes using the first way. Therefore, total points = (8 - 0) + (4 - 0) + (4 - 0) = 16.

 

Constraints:

  • n == coins.length
  • 2 <= n <= 105
  • 0 <= coins[i] <= 104
  • edges.length == n - 1
  • 0 <= edges[i][0], edges[i][1] < n
  • 0 <= k <= 104

代码结果

运行时间: 613 ms, 内存: 64.3 MB


/*
题目思路:
1. 使用流操作构建树的结构。
2. 使用DFS从根节点开始遍历树。
3. 在每个节点,根据情况选择收集金币的方法。
4. 记录并返回最大积分。
*/

import java.util.*;
import java.util.stream.*;

class Solution {
    private List<List<Integer>> tree;
    private int[] coins;
    private int k;
    private int maxScore = 0;

    public int collectCoins(int[][] edges, int[] coins, int k) {
        int n = coins.length;
        this.coins = coins;
        this.k = k;
        tree = IntStream.range(0, n).mapToObj(i -> new ArrayList<Integer>()).collect(Collectors.toList());
        Arrays.stream(edges).forEach(edge -> {
            tree.get(edge[0]).add(edge[1]);
            tree.get(edge[1]).add(edge[0]);
        });
        boolean[] visited = new boolean[n];
        dfs(0, visited);
        return maxScore;
    }

    private void dfs(int node, boolean[] visited) {
        visited[node] = true;
        int childScore = 0;
        for (int child : tree.get(node)) {
            if (!visited[child]) {
                dfs(child, visited);
                childScore += coins[child];
            }
        }
        int option1 = coins[node] - k;
        int option2 = childScore / 2;
        int score = Math.max(option1, option2);
        maxScore += score;
        coins[node] = score;
    }
}

解释

方法:

这个题解采用了深度优先搜索(DFS)结合记忆化的方式来解决问题。首先,构建了一个邻接表g来表示树的结构。然后,定义了一个递归函数dfs,用于从某个节点x开始,尝试以两种不同的策略收集金币,并返回可能的最大金币数量。这两种策略分别是:1) 当前节点的金币数右移half位然后减去k;2) 当前节点的金币数右移half+1位。对于每个节点,递归地调用其子节点的dfs函数,并将结果累加到当前节点的策略中。最后,对于每个节点,取两种策略中的最大值。递归的终止条件是当half超过13时,不再递归调用half+1的情况。整个过程从根节点开始,递归遍历整棵树,最终返回根节点可以获得的最大金币数。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
如何保证在使用DFS和记忆化的过程中不会出现循环调用或者重复计算?
在DFS的实现中,通过传递一个父节点参数`fa`来避免循环调用。当递归访问子节点时,检查子节点是否为当前节点的父节点,如果不是,则进行递归调用。这样可以确保不会向父节点递归,从而避免循环。此外,使用`@cache`装饰器来缓存已计算的结果,防止对同一状态的重复计算。这种记忆化方法显著提高了算法的效率,避免了重复的工作。
🦆
在题解中提到使用策略1和策略2处理金币,为什么选择右移和减去k作为策略?这样的操作有什么特殊意义吗?
题解中的两种策略通过不同的位移操作来模拟可能的金币收集策略,提供灵活的选择以最大化金币收集。策略1通过右移`half`位然后减去固定值`k`来模拟一种收集成本或损耗,可能代表在收集过程中的某种损耗或代价。策略2通过进一步右移一位来代表在不同条件下的另一种可能性或保守估计。这两种策略允许算法在不同层级递归中权衡并选择最优解,而位移操作本身是一种高效的计算方式,适合快速处理大量数据。
🦆
递归函数`dfs`的参数`half`在递归过程中具体起到了什么作用,它是如何影响策略选择的?
参数`half`在递归函数`dfs`中用于控制金币的位移操作。随着`half`的增加,金币数会被进一步右移,这意味着在递归深入的过程中采取更加保守的金币计算策略。`half`的变化允许函数在不同深度的递归中尝试不同程度的位移,从而探索收集金币的不同策略。这是一种灵活调整策略,以适应可能的最大收益。递归的终止条件是`half`超过13时,不再递归调用`half+1`的情况,这避免了过度的位移导致的无效运算。

相关问题