Woodyiiiiiii / LeetCode

My private record of Leetcode solution

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Leetcode 2646. Minimize the Total Price of the Trips

Woodyiiiiiii opened this issue · comments

2646. Minimize the Total Price of the Trips

好题。综合了很多**。

首先看清题意。题目要求的是在第一次操作前就要考虑是否对结点价值减半了,同时要求选择减半的结点不相邻。那么这里我陷入了思维误区,以为贪心求两种分类就够了(从0结点选择或不选择开始分成两类),其实很明显错误了,因为每个结点的价值不同,可能会出现选择减半的结点距离很远的情况。那这种就想到了337. House Robber III的**,也就是树形DP

显然这种non-adjacent的题型都是用House Robber的DP思路。

又因为可以观察到对每个trip来说,路径都是固定的(因为是树),同时结点之间关联不大。所以接下来我们根据结点数量少的限制,可以对每个trip跑一遍DFS,记录路径经过的结点次数,最后汇总,DP计算总价值。

class Solution {
    Map<Integer, Set<Integer>> graph;
    int[] price;
    int[] count;
    List<Integer> routine;

    public int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {
        this.graph = new HashMap<>();
        for (int i = 0; i < n; i++) {
            graph.put(i, new HashSet<>());
        }
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        }
        this.price = price;

        count = new int[n];
        for (int[] trip : trips) {
            routine = new ArrayList<>();
            List<Integer> path = new ArrayList<>();
            path.add(trip[0]);
            findPath(-1, trip[0], trip[1], path);
            for (int i : routine) {
                count[i]++;
            }
        }

        int[] res = dfs(0, -1);
        return Math.min(res[0], res[1]);
    }

    private int[] dfs(int cur, int pre) {
        int[] res = new int[2];
        int notHalve = price[cur] * count[cur];
        int halve = notHalve / 2;
        for (int next : graph.get(cur)) {
            if (next == pre) {
                continue;
            }
            int[] nextRes = dfs(next, cur);
            halve += nextRes[0];
            notHalve += Math.min(nextRes[0], nextRes[1]);
        }
        res[0] = notHalve;
        res[1] = halve;
        return res;
    }

    private void findPath(int pre, int cur, int end, List<Integer> path) {
        if (cur == end) {
            routine = new ArrayList<>(path);
            return;
        }
        for (int next : graph.get(cur)) {
            if (next == pre) {
                continue;
            }
            path.add(next);
            findPath(cur, next, end, path);
            path.remove(path.size() - 1);
        }
    }
}

时间复杂度O(nm),其中m是trip长度;空间复杂度O(n)。

第二种方法是用Tarjan 离线 LCA + 树上差分,太难了看详解。

相关联的题目:

337. House Robber III

下面是我之前的做法,深度后序遍历,其中创建Map缓存防止重复子问题出现。

class Solution {
    
    Map<TreeNode, Integer> memo = new HashMap<>();
    
    public int rob(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (memo.containsKey(root)) {
            return memo.get(root);
        }
        
        int l = rob(root.left);
        int r = rob(root.right);
        int ll = root.left == null ? 0 : rob(root.left.left);
        int lr = root.left == null ? 0 : rob(root.left.right);
        int rr = root.right == null ? 0 : rob(root.right.right);
        int rl = root.right == null ? 0 : rob(root.right.left);
        
        int max = Math.max(l + r, root.val + ll + lr + rr + rl);
        
        memo.put(root, max);
        
        return max;
        
    }
}

其中发现,每个结点最后的值只跟他的孩子结点有关,所以我们可以设计后序树形DP,这样省去了缓存。

class Solution {
    public int rob(TreeNode root) {
        int[] res = dfs(root);
        return Math.max(res[0], res[1]);
    }

    private int[] dfs(TreeNode root) {
        int[] res = new int[2];
        if (root == null) {
            return res;
        }
        res[0] = root.val;
        int[] left = dfs(root.left);
        int[] right = dfs(root.right);
        res[1] += Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        res[0] += (left[1] + right[1]);
        return res;
    }
}

相关资料