【每日一题Day56】LC1679检查边长度限制的路径是否存在 | 并查集

简介: 思路:将输入的 edgeList转换成邻接表 adList,维护一个小顶堆 pq 可以暴力计算出查询数组queries中各个起点到各个终点边长度最短的路径,从而判断是否存在办长度限制的路径

检查边长度限制的路径是否存在【LC1679】


给你一个 n 个点组成的无向图边集 edgeList ,其中 edgeList[i] = [ui, vi, disi] 表示点 ui 和点 vi 之间有一条长度为 disi 的边。请注意,两个点之间可能有 超过一条边


给你一个查询数组queries ,其中 queries[j] = [pj, qj, limitj] ,你的任务是对于每个查询 queries[j] ,判断是否存在从 pj 到 qj 的路径,且这条路径上的每一条边都 严格小于 limitj 。


请你返回一个 布尔数组 answer ,其中 answer.length == queries.length ,当 queries[j] 的查询结果为 true 时, answer 第 j 个值为 true ,否则为 false 。


An undirected graph of n nodes is defined by edgeList, where edgeList[i] = [ui, vi, disi] denotes an edge between nodes ui and vi with distance disi. Note that there may be multiple edges between two nodes.


Given an array queries, where queries[j] = [pj, qj, limitj], your task is to determine for each queries[j] whether there is a path between pj and qj such that each edge on the path has a distance strictly less than limitj .


Return a boolean array answer, where answer.length == queries.length and the jth value of answer is true if there is a path for queries[j] is true, and false otherwise.


同门阳了 我指希望不要太难受 这周把这类并查集搞定


BFS【超时】

  • 思路:将输入的 edgeList转换成邻接表 adList,维护一个小顶堆 pq 可以暴力计算出查询数组queries中各个起点到各个终点边长度最短的路径,从而判断是否存在办长度限制的路径


。pq 中的元素为节点编号以及起点到该节点的路径长度,并以路径长度为比较元素。每次取出未访问过的节点中的路径最短的节点,并访问其邻接点,若路径长度仍小于等于 queries[2]且未访问过,可将其放入 pq,直至 pq 为空或 边大于queries[2]。


  • 实现


class Solution {
    public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) {
        boolean[] res = new boolean[queries.length];
        // 构建邻接表
        List<int[]>[] adList = new List[n];
        for (int i = 0; i < n; i++){
            adList[i] = new ArrayList<int[]>();
        }
        for (int[] edge : edgeList){
            int u = edge[0], v = edge[1], dis = edge[2];
            adList[u].add(new int[]{v, dis});
            adList[v].add(new int[]{u, dis});
        }
        for (int i = 0; i < queries.length; i++){
            int start = queries[i][0], end = queries[i][1], limit = queries[i][2];
            PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);
            pq.offer(new int[]{0, start});
            Set<Integer> visited = new HashSet<>();
            // 寻找路径
            while(!pq.isEmpty() && pq.peek()[0] < limit){
                int[] poll = pq.poll();
                int dis = poll[0], u = poll[1];
                if (!visited.add(u)){
                    continue;
                }
                if (u == end){
                    res[i] = true;
                    break;
                }
                // 加入下一个可以访问的节点
                for (int[] next : adList[u]){
                    int v = next[0], disNext = next[1];
                    if (!visited.contains(v)){
                        pq.offer(new int[]{disNext, v});
                    }
                }
            }
        }
        return res;
    }
}


。复杂度


  • 时间复杂度:O(E×logV×m),V  为节点数,即 n ,E为输入edgeList 的长度,m 为queries的长度。邻接表 adList的时间复杂度为O(E),搜索路径的时间复杂度为O(E×log⁡V)。


  • 空间复杂度:O(V+E),邻接表 adList 的空间复杂度为O(E),pq 的空间复杂度为O(E),visited 的空间复杂度为 O ( V ) ,总的空间复杂度为 O(V+E)。


并查集


  • 离线查询:对于一道题目会给出若干询问,而这些询问是全部提前给出的,也就是说,你不必按照询问的顺序依次对它们进行处理,而是可以按照某种顺序(例如全序、偏序(拓扑序)、树的 DFS 序等)或者把所有询问看成一个整体(例如整体二分、莫队算法等)进行处理。


  • 思路:


。使用并查集维护图的连通性,对于queries[i],我们可以将edgeList中所有长度小于limit的边加入到并查集中,然后使用并查集查询u和v是否属于同一个集合。如果 u和 v属于同一个集合,则说明存在从 u到 v的路径,且这条路径上的每一条边的长度都严格小于 limit,查询返回true,否则查询返回false。


。因此可以将queries按照limit从小到大进行排序,并将edgeList按照距离dis从小到大进行排序,使用指针k定位上一次查询中不满足limit要求的长度最小的边,以便于使用离线查询的思维处理queries中的询问,降低时间复杂度


  • 实现


1.排序


2.依次遍历 queries:如果 k 指向的边的长度小于对应查询的 limit,则将该边加入并查集中,然后将 k加 1,直到 k指向的边不满足要求;最后根据并查集查询对应的u和v是否属于同一集合来保存查询的结果。


class Solution {
    private int[] father;
    public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) {
        // 记录初始下标
        Integer[] index = new Integer[queries.length];
        for (int i = 0; i < queries.length; i++){
            index[i] = i;
        }
        // 排序
        Arrays.sort(edgeList, (a, b) -> a[2] - b[2]);
        Arrays.sort(index, (a, b) -> queries[a][2] - queries[b][2]);
        // 并查集初始化
        father = new int[n];
        for (int i = 0; i < n; i++){
            father[i] = i;
        }
        boolean[] res = new boolean[queries.length];
        int k = 0;
        for (int i : index){
            while (k < edgeList.length && edgeList[k][2] < queries[i][2]){
                join(edgeList[k][0],edgeList[k][1]);
                k++;
            }
            res[i] = find(queries[i][0]) == find(queries[i][1]);
        }
        return res;
    }
    private int find(int u){
        if (u == father[u]){
            return u;
        }
        father[u] = find(father[u]);
        return father[u];
    }
    private void join (int u, int v){
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }
}


。复杂度


  • 时间复杂度:O(ElogE+mlogm+(E+m)logn+n),n 为节点数,E 为输入edgeList 的长度,m 为queries的长度。对edgeList和queries 排序时间复杂度分别为 O(ElogE)和 O(mlogm),并查集初始化的时间复杂度为O(n),并查集查询和合并的时间复杂度为O((E+m)logn)。


  • 空间复杂度:O(logE+m+n),保存并查集的空间复杂度为O(n),保存下标的空间复杂度为O(m),对edgeList排序的空间复杂度为O(logE),对queries排序的空间复杂度可以忽略
目录
相关文章
|
6月前
【每日一题Day285】LC980不同路径 III | 回溯
【每日一题Day285】LC980不同路径 III | 回溯
60 0
|
6月前
【每日一题Day191】LC2423删除字符使频率相同 | 枚举 分类讨论
【每日一题Day191】LC2423删除字符使频率相同 | 枚举 分类讨论
46 0
|
6月前
【每日一题Day189】LC1048最长字符串链 | dp+排序
【每日一题Day189】LC1048最长字符串链 | dp+排序
48 0
|
算法
【代码随想录】LC 209. 长度最小的子数组
利用两层循环,第一层循环枚举子数组的起点位置,第二层循环枚举子数组的终点位置,第二层循环中可以同时来统计当前子数组的和,如果符合题目条件则更新length,否则继续循环,直至两层循环结束,返回题目要求的值,算法结束。
58 0
|
6月前
【每日一题Day117】LC1234替换子串得到平衡字符串 | 双指针
【每日一题Day117】LC1234替换子串得到平衡字符串 | 双指针
44 0
|
24天前
|
JavaScript
力扣3333.找到初始输入字符串Ⅱ
【10月更文挑战第9天】力扣3333.找到初始输入字符串Ⅱ
31 1
|
6月前
【每日一题Day278】LC2500删除每行中的最大值 | 排序+模拟
【每日一题Day278】LC2500删除每行中的最大值 | 排序+模拟
47 0
|
6月前
|
机器学习/深度学习 存储
【每日一题Day112】LC1233删除子文件夹 | 排序 字典树
【每日一题Day112】LC1233删除子文件夹 | 排序 字典树
43 1
|
6月前
【每日一题Day138】LC1653使字符串平衡的最少删除次数 | 前后缀 动态规划
【每日一题Day138】LC1653使字符串平衡的最少删除次数 | 前后缀 动态规划
60 0
|
6月前
【每日一题Day151】LC1625执行操作后字典序最小的字符串 | BFS
【每日一题Day151】LC1625执行操作后字典序最小的字符串 | BFS
37 0