【Leetcode】684. Redundant Connection

題目地址:

https://leetcode.com/problems/redundant-connection/

給定一個無向圖,以二維數組,每個位置存一條邊的形式給出。問刪去哪條邊,這個圖會變成一棵樹。題目保證答案存在。並且返回那個下標最大的滿足條件的邊。

法1:DFS。題目的本質是找圈,所以可以用DFS來做。先從一個頂點開始遍歷,找到圈後返回路徑,然後解析出圈,接着再找到下標最大的邊即可。代碼如下:

import java.util.*;

public class Solution {
    // 開一個邊的類,以便於最後存入哈希表方便查找滿足條件的”最後一個“邊
    class Edge {
        int from, to;
        Edge() {}
        Edge(int from, int to) {
            this.from = from;
            this.to = to;
        }
    
        @Override
        public boolean equals(Object another) {
            Edge edge = (Edge) another;
            return from == edge.from && to == edge.to;
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(from, to);
        }
    }
    
    public int[] findRedundantConnection(int[][] edges) {
        Map<Integer, Set<Integer>> graph = new HashMap<>();
        Map<Edge, Integer> edgeIdx = new HashMap<>();
        // 用鄰接表建圖,並把每個邊的下標存入edgeIdx這個哈希表
        for (int i = 0; i < edges.length; i++) {
            graph.putIfAbsent(edges[i][0], new HashSet<>());
            graph.get(edges[i][0]).add(edges[i][1]);
            graph.putIfAbsent(edges[i][1], new HashSet<>());
            graph.get(edges[i][1]).add(edges[i][0]);
            edgeIdx.put(new Edge(edges[i][0], edges[i][1]), i);
        }
        
        List<Integer> path = new ArrayList<>();
        boolean[] visited = new boolean[edges.length + 1];
        
        // 由於環一定存在,所以就直接從任意一個點開始DFS即可
        dfs(1, 1, graph, visited, path);
        
        // 開始找環,發現第一個重複數字後就知道了環的位置了
        Map<Integer, Integer> map = new HashMap<>();
        int start = 0, end = 0;
        for (int i = 0; i < path.size(); i++) {
            if (map.containsKey(path.get(i))) {
                start = map.get(path.get(i));
                end = i;
            }
            map.put(path.get(i), i);
        }
        
        // 分離出環後查看哪條邊的下標最大,最後返回
        int[] res = null;
        int idx = -1;
        for (int i = start; i < end; i++) {
            Edge edge = new Edge();
            edge.from = Math.min(path.get(i), path.get(i + 1));
            edge.to = Math.max(path.get(i), path.get(i + 1));
            
            if (idx < edgeIdx.get(edge)) {
                idx = edgeIdx.get(edge);
                res = edges[idx];
            }
        }
        
        return res;
    }
    
    // 從v開始做DFS,並且v是由w走到的;返回是否找到環
    private boolean dfs(int v, int w, Map<Integer, Set<Integer>> graph, boolean[] visited, List<Integer> path) {
        path.add(v);
        visited[v] = true;
        if (graph.containsKey(v)) {
            for (int next : graph.get(v)) {
            	// 不走回頭路
                if (next == w) {
                    continue;
                }
                // 找到環了,返回true
                if (visited[next]) {
                    path.add(next);
                    return true;
                }
                // 否則繼續尋找環
                if (dfs(next, v, graph, visited, path)) {
                    return true;
                }
            }
        }
        // 走到了死衚衕,回溯前要恢復現場,把死路走到的點刪掉,並返回false
        path.remove(path.size() - 1);
        return false;
    }
}

時空複雜度O(V+E)O(V+E)

法2:並查集(disjoint set)。每次遇到一條邊,就將邊的兩個端點union起來,如果遇到了某一條邊,發現其兩個端點已經屬於同一個集合了,就直接返回。代碼如下:

import java.util.HashSet;
import java.util.Set;

public class Solution {
    
    class UnionFind {
        int[] parent;
        int[] rank;
        
        UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                rank[i] = 1;
            }
        }
        
        public int find(int p) {
            if (p != parent[p]) {
                parent[p] = find(parent[p]);
            }
            
            return parent[p];
        }
        // 如果p和q不屬於同一個集合,則將它們的所在集合合併起來,並返回true;
        // 否則若p和q屬於同一個集合,則返回false
        public boolean union(int p, int q) {
            int pid = find(p), qid = find(q);
            if (pid == qid) {
                return false;
            }
            
            if (rank[pid] < rank[qid]) {
                parent[pid] = qid;
            } else if (rank[pid] > rank[qid]) {
                parent[qid] = pid;
            } else {
                parent[pid] = qid;
                rank[qid]++;
            }
            
            return true;
        }
    }
    
    public int[] findRedundantConnection(int[][] edges) {
    	// 開一個set數一下這個圖一共有多少個頂點
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < edges.length; i++) {
            set.add(edges[i][0]);
            set.add(edges[i][1]);
        }
        
        UnionFind uf = new UnionFind(set.size());
        for (int i = 0; i < edges.length; i++) {
            int p = edges[i][0], q = edges[i][1];
            // 發現了p - 1和q - 1屬於同一個集合,則將這條邊返回
            if (!uf.union(p - 1, q - 1)) {
                return edges[i];
            }
        }
        
        return null;
    }
}

時間複雜度O(nlogn)O(n\log^*n)log\log^*指的是iterative logarithm),空間O(n)O(n)

算法正確性證明:
首先題目已經保證圖連通,並且只需要去掉一條邊,就會成爲樹。那麼當第一次發現兩個點ppqq處於同一個集合的時候,就已經出現了環(因爲在沒有pqpq這條邊的時候,就已經存在一條通路了,然後再加上pqpq這條邊就成爲了環),由於題目保證只需要去掉一條邊就成爲樹,所以這一條邊就是最終答案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章