算法每日一题: 边权重均等查询 | 公共子祖先

大家好,我是星恒,今天给大家带来的是一道图里面有关公共子祖先的题目,理解起来简单,大家

题目:leetcode 2846

现有一棵由 n 个节点组成的无向树,节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ui, vi, wi] 表示树中存在一条位于节点 ui 和节点 vi 之间、权重为 wi 的边。另给你一个长度为 m 的二维整数数组 queries ,其中 queries[i] = [ai, bi] 。对于每条查询,请你找出使从 ai 到 bi 路径上每条边的权重相等所需的 最小操作次数 。在一次操作中,你可以选择树上的任意一条边,并将其权重更改为任意值。
注意:

  • 查询之间 相互独立 的,这意味着每条新的查询时,树都会回到 初始状态
  • 从 ai 到 bi的路径是一个由 不同 节点组成的序列,从节点 ai 开始,到节点 bi 结束,且序列中相邻的两个节点在树中共享一条边。

返回一个长度为 m 的数组 answer ,其中 answer[i] 是第 i 条查询的答案。

示例 1:

输入:n = 7, edges = [[0,1,1],[1,2,1],[2,3,1],[3,4,2],[4,5,2],[5,6,2]], queries = [[0,3],[3,6],[2,6],[0,6]]
输出:[0,0,1,3]
解释:第 1 条查询,从节点 0 到节点 3 的路径中的所有边的权重都是 1 。因此,答案为 0 。
第 2 条查询,从节点 3 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 0 。
第 3 条查询,将边 [2,3] 的权重变更为 2 。在这次操作之后,从节点 2 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 1 。
第 4 条查询,将边 [0,1]、[1,2]、[2,3] 的权重变更为 2 。在这次操作之后,从节点 0 到节点 6 的路径中的所有边的权重都是 2 。因此,答案为 3 。
对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。

示例 2:

输入:n = 8, edges = [[1,2,6],[1,3,4],[2,4,6],[2,5,3],[3,6,6],[3,0,8],[7,0,2]], queries = [[4,6],[0,4],[6,5],[7,4]]
输出:[1,2,2,3]
解释:第 1 条查询,将边 [1,3] 的权重变更为 6 。在这次操作之后,从节点 4 到节点 6 的路径中的所有边的权重都是 6 。因此,答案为 1 。
第 2 条查询,将边 [0,3]、[3,1] 的权重变更为 6 。在这次操作之后,从节点 0 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 2 。
第 3 条查询,将边 [1,3]、[5,2] 的权重变更为 6 。在这次操作之后,从节点 6 到节点 5 的路径中的所有边的权重都是 6 。因此,答案为 2 。
第 4 条查询,将边 [0,7]、[0,3]、[1,3] 的权重变更为 6 。在这次操作之后,从节点 7 到节点 4 的路径中的所有边的权重都是 6 。因此,答案为 3 。
对于每条查询 queries[i] ,可以证明 answer[i] 是使从 ai 到 bi 的路径中的所有边的权重相等的最小操作次数。

提示:

  • 1 <= n <= 104
  • edges.length == n - 1
  • edges[i].length == 3
  • 0 <= ui, vi < n
  • 1 <= wi <= 26
  • 生成的输入满足 edges 表示一棵有效的树
  • 1 <= queries.length == m <= 2 * 104
  • queries[i].length == 2
  • 0 <= ai, bi < n

分析:

我们先看这道题的最直接的问题,如何寻找修改最少次数?我们只需要贪心的让两个点之间所有边,变为边权重出现最多的次数的权重,例如寻找a和b两点间修改的最小次数,如果ab两点间所有边中,权重2出现的次数最多,我们就让所有边的值改为2,这样修改的次数就最少了

好,知道这一点,我们的问题就变成了寻找两点间所有权重中,哪个权重出现次数最多。我们从提示中可以看出,权重的取值范围为1 - 26,我们可以计算每个点到根节点(开始节点)的每个权重出现的次数,然后当我们计算两点之间的最小 权重出现次数 时,这样计算:

我们还是使用之前的例子,计算a - b间:

count[a][i] + count[b][i] - 2count[c][i]

  • c是公共子祖先
  • count[a][i] 为节点a到根节点,权重为i的边出现的次数

难点:寻找公共祖先,这个大家可以在网上了解一下,这里使用Tarjan 算法

推荐链接:https://oi.wiki/graph/lca/#tarjan-算法

代码:

typescript 复制代码
class Solution {
    static final int W = 26;

    public int[] minOperationsQueries(int n, int[][] edges, int[][] queries) {
        int m = queries.length;
        Map<Integer, Integer>[] neighbors = new Map[n];
        for (int i = 0; i < n; i++) {
            neighbors[i] = new HashMap<Integer, Integer>();
        }
        for (int[] edge : edges) {
            neighbors[edge[0]].put(edge[1], edge[2]);
            neighbors[edge[1]].put(edge[0], edge[2]);
        }
        List<int[]>[] queryArr = new List[n];
        for (int i = 0; i < n; i++) {
            queryArr[i] = new ArrayList<int[]>();
        }
        for (int i = 0; i < m; i++) {
            queryArr[queries[i][0]].add(new int[]{queries[i][1], i});
            queryArr[queries[i][1]].add(new int[]{queries[i][0], i});
        }

        int[][] count = new int[n][W + 1];
        boolean[] visited = new boolean[n];
        int[] uf = new int[n];
        int[] lca = new int[m];
        tarjan(0, -1, neighbors, queryArr, count, visited, uf, lca);
        int[] res = new int[m];
        for (int i = 0; i < m; i++) {
            int totalCount = 0, maxCount = 0;
            for (int j = 1; j <= W; j++) {
                int t = count[queries[i][0]][j] + count[queries[i][1]][j] - 2 * count[lca[i]][j];
                maxCount = Math.max(maxCount, t);
                totalCount += t;
            }
            res[i] = totalCount - maxCount;
        }
        return res;
    }

    public void tarjan(int node, int parent, Map<Integer, Integer>[] neighbors, List<int[]>[] queryArr, int[][] count, boolean[] visited, int[] uf, int[] lca) {
        if (parent != -1) {
            System.arraycopy(count[parent], 0, count[node], 0, W + 1);
            count[node][neighbors[node].get(parent)]++;
        }
        uf[node] = node;
        for (int child : neighbors[node].keySet()) {
            if (child == parent) {
                continue;
            }
            tarjan(child, node, neighbors, queryArr, count, visited, uf, lca);
            uf[child] = node;
        }
        for (int[] pair : queryArr[node]) {
            int node1 = pair[0], index = pair[1];
            if (node != node1 && !visited[node1]) {
                continue;
            }
            lca[index] = find(uf, node1);
        }
        visited[node] = true;
    }

    public int find(int[] uf, int i) {
        if (uf[i] == i) {
            return i;
        }
        uf[i] = find(uf, uf[i]);
        return uf[i];
    }
}

示例:

  • 提示很重要
  • 公共子祖先
    • 如果大家是为了面试,不需要了解这个算法
    • 如果是为了蓝桥杯,建议看一下这个算法

如果大家有什么思考和问题,可以在评论区讨论,也可以私信我,很乐意为大家效劳。

好啦,今天的每日一题到这里就结束了,如果大家觉得有用,可以可以给我一个小小的赞呢,我们下期再见!

相关推荐
XY.散人12 分钟前
初识Linux · 地址空间
linux·算法
小林up40 分钟前
数据结构与算法笔记7:最小生成树-Prim和Kruskal算法
算法
十七算法实验室1 小时前
Matlab实现鲸鱼优化算法优化回声状态网络模型 (WOA-ESN)(附源码)
算法
码农小苏241 小时前
力扣刷题--476. 数字的补数【简单】
数据结构·算法·leetcode
流星白龙2 小时前
【C++算法】6.双指针_有效三角形的个数
开发语言·c++·算法
是小比特2 小时前
二叉搜索树(c++版)
数据结构·算法
nuo5342022 小时前
数学期望专题
c语言·c++·算法·概率论
霍金的微笑2 小时前
LeetCode
算法·leetcode·职场和发展
菜鸟求带飞_3 小时前
算法打卡:第十一章 图论part10
java·数据结构·算法·图论
窜天遁地大吗喽3 小时前
分层图 的尝试学习 1.0
学习·算法·图论