2603. 收集树中金币

给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间有一条边。再给你一个长度为 n 的数组 coins ,其中 coins[i] 可能为 0 也可能为 1 ,1 表示节点 i 处有一个金币。

一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:

收集距离当前节点距离为 2 以内的所有金币,或者
移动到树中一个相邻节点。
你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。

如果你多次经过一条边,每一次经过都会给答案加一。

很有意思的一道题目。很难想到拓扑排序。拓扑排序经典要素,拓扑关系,度,队列。自底向上逐渐逼近。

class Solution {
    public int collectTheCoins(int[] coins, int[][] edges) {
        /**
        因为求的是最少的边数
        主要思想是先逐步删去无用的边。
        第一步是收集没有金币的叶子,然后将其与父节点的边删掉
        第二步是收集有金币的叶子,然后删掉与父节点的边及其父节点与叶节点的边
         */
        int n = coins.length;

        // 存放全局拓扑关系
        List<Integer>[] g = new ArrayList[n];
        Arrays.setAll(g, e->new ArrayList());
        // 入度
        int[] degrees = new int[n];
        var q = new ArrayDeque<Integer>();
        int leftEdges = n-1;
        for(int[] edge:edges) {
            int x = edge[0], y = edge[1];
            g[x].add(y);
            g[y].add(x);
            degrees[x] ++;
            degrees[y] ++;
        }
        // 第一步是收集没有金币的叶子,然后将其与父节点的边删掉
        for(int i=0; i<n; i++) {
            if(degrees[i] == 1 && coins[i] == 0) {
                q.offer(i);
            }
        }
        while(!q.isEmpty()) {
            leftEdges --;
            for(int node:g[q.pop()]) {
                degrees[node] --;
                if(degrees[node] == 1 && coins[node] == 0) {
                    q.offer(node);
                }
            }

        }

        // 第二步是收集有金币的叶子,然后删掉与父节点的边及其父节点与叶节点的边
        for(int i=0; i<n; i++) {
            if(degrees[i] == 1 && coins[i] == 1) {
                q.offer(i);
            }
        }
        leftEdges -= q.size(); 
        
        while(!q.isEmpty()) {
            for(int node:g[q.pop()]) {
                degrees[node] --;
                if(degrees[node] == 1) {
                    leftEdges --;
                }
            }
        }
        return Math.max(2*leftEdges, 0);
    }
}

你可能感兴趣的:(java,数据结构,算法)