Leetcode 题解 - 图

184 阅读2分钟

image-20211113085518271

二分图

如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。

1. 判断是否为二分图

Leetcode (opens new window)/ 力扣

(1)深度优先遍历

class Solution {
    // 未着色
    private static final int UNCOLORED = 0;
    // 红色
    private static final int RED = 1;
    // 绿色
    private static final int GREEN = 2;

    private int[] color;
    
    private boolean flag;

    // 转换成图的染色问题
    public boolean isBipartite(int[][] graph) {
        int n = graph.length;
        this.flag = true;
        this.color = new int[n];
        Arrays.fill(this.color, UNCOLORED);

        for (int i = 0; i < n && flag; i++) {
            if (color[i] == UNCOLORED) {
                dfs(i, RED, graph);
            }
        }

        return flag;
    }

    // 深度优先遍历
    private void dfs(int node, int c, int[][] graph) {
        color[node] = c;
        int cNext = c == RED ? GREEN : RED;

        for (int temp : graph[node]) {
            if (color[temp] == UNCOLORED) {
                dfs(temp, cNext, graph);

                if (!flag) {
                    return ;
                }
            } else if (color[temp] != cNext) {
                this.flag = false;
                return ;
            }
        }
    }
}

(2)广度优先遍历

class Solution {
    private static final int UNCOLORED = 0;
    private static final int RED = 1;
    private static final int GREEN = 2;
    private int[] color;

    public boolean isBipartite(int[][] graph) {
        int n = graph.length;
        this.color = new int[n];
        Arrays.fill(this.color, UNCOLORED);

        for (int i = 0; i < n; i++) {
            if (color[i] == UNCOLORED) {
                Queue<Integer> queue = new LinkedList<>();
                queue.offer(i);
                color[i] = RED;

                while (!queue.isEmpty()) {
                    int node = queue.poll();
                    int cNext = color[node] == RED ? GREEN : RED;

                    for (int temp : graph[node]) {
                        if (color[temp] == UNCOLORED) {
                            queue.offer(temp);
                            color[temp] = cNext;
                        } else if (color[temp] != cNext) {
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }
}


拓扑排序

常用于在具有先序关系的任务规划中。

1. 课程安排的合法性

Leetcode (opens new window)/ 力扣

(1)BFS

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 1 生成入度表,并将对应的关系用集合表示
        int[] counts = new int[numCourses];
        List<List<Integer>> edges = new ArrayList<>();

        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<>());
        }

        for (int[] value : prerequisites) {
            edges.get(value[1]).add(value[0]);
            counts[value[0]]++;
        }

        // 2 初始化队列,将入度为 0 的队列入队
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (counts[i] == 0) {
                queue.add(i);
            }
        }

        // 3 BFS,拓扑排序
        int visited = 0;
        while (!queue.isEmpty()) {
            visited++;
            int i = queue.poll();

            for (int value : edges.get(i)) {
                // 入度减 1
                counts[value]--;
                // 当入度为 0 时,加入队列
                if (counts[value] == 0) {
                    queue.add(value);
                }
            }
        }

        // 如果不相等,说明有环
        return visited == numCourses;
    }
}

(2)DFS

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> list = new ArrayList<>();

        for (int i = 0; i < numCourses; i++) {
            list.add(new ArrayList<>());
        }

        int[] flags = new int[numCourses];
        for (int[] cp : prerequisites) {
            list.get(cp[1]).add(cp[0]);
        }

        for (int i = 0; i < numCourses; i++) {
            if (!dfs(list, flags, i)) {
                return false;
            }
        }

        return true;
    }

    private boolean dfs(List<List<Integer>> list, int[] flags, int i) {
        if (flags[i] == 1) {
            return false;
        } 
        if (flags[i] == -1) {
            return true;
        }
        flags[i] = 1;

        for(Integer j : list.get(i)) {
            if(!dfs(list, flags, j)) {
                return false;
            }
        } 
        flags[i] = -1;
        return true;  
    }
}


2. 课程安排的顺序

Leetcode (opens new window)/ 力扣

(1)BFS

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 建立入度表,用集合表示图的关系
        int[] counts = new int[numCourses];
        // 图的关系集合
        List<List<Integer>> edges = new ArrayList<>();
        // 初始化集合
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<>());
        }
        // 将图之间对应的关系添加到集合中
        for (int[] values : prerequisites) {
            // [1, 0] 对应的关系为 0 -> 1
            // 因为是先修了 0 才能修 1
            edges.get(values[1]).add(values[0]);
            // 对应的入度加 1
            counts[values[0]]++;
        }

        // 将入度为 0 的课程入队列
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (counts[i] == 0) {
                queue.add(i);
            }
        }

        // BFS
        int[] result = new int[numCourses];
        int index = 0;
        while (!queue.isEmpty()) {
            int i = queue.poll();
            // 加入到结果数组中
            result[index++] = i;

            for (int value : edges.get(i)) {
                // 入度减 1
                counts[value]--;
                // 如果入度为 0 就加入到队列中去
                if (counts[value] == 0) {
                    queue.add(value);
                }
            }
        }
        // 如果不等于课程数
        if (index != numCourses) {
            // 返回空数组
            return new int[]{};
        }

        return result;
    }
}

(2)DFS

class Solution {
    // 存储有向图
    private List<List<Integer>> edges;
    // 标记每个节点的状态,0 未搜索 1 搜索中 2 已搜索
    private int[] visited;
    // 用数组来模拟栈,n - 1 为栈顶,0 为栈顶
    private int[] result;
    // 判断有向图中是否有环
    private boolean valid = true;
    // 栈下标
    private int index;
    
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        result = new int[numCourses];
        index = numCourses - 1;
        for (int[] value : prerequisites) {
            edges.get(value[1]).add(value[0]);
        }

        for (int i = 0; i < numCourses && valid; i++) {
            if (visited[i] == 0) {
                dfs(i);
            }
        }

        if (valid == false) {
            return new int[]{};
        }

        return result;
    }

    // 深度优先遍历
    private void dfs(int i) {
        // 将节点标记为搜索中
        visited[i] = 1;
        // 搜索相邻节点,一旦发现有环就停止搜索
        for (int v : edges.get(i)) {
            if (visited[v] == 0) {
                dfs(v);
                // 如果有环
                if (!valid) {
                    return ;
                }
            // 如果重复搜索了节点,说明有环
            } else if (visited[v] == 1) {
                valid = false;
                return ;
            }

        }
        // 将节点标记为已搜索
        visited[i] = 2;
        // 入栈
        result[index--] = i;
    }
}


并查集

并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。

1. 冗余连接

Leetcode (opens new window)/ 力扣

class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length;
        int[] parent = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            parent[i] = i;
        }

        for (int i = 0; i < n; i++) {
            int[] edge = edges[i];
            int node1 = edge[0];
            int node2 = edge[1];

            if (find(parent, node1) != find(parent, node2)) {
                union(parent, node1, node2);
            } else {
                return edge;
            }
        }

        return new int[0];
    }

    public void union(int[] parent, int index1, int index2) {
        parent[find(parent, index1)] = find(parent, index2);
    }

    public int find(int[] parent, int index) {
        if (parent[index] != index) {
            parent[index] = find(parent, parent[index]);
        }

        return parent[index];
    }
}