Graph类型题总结

Graph类型题总结

Graph类型题常见的有1. Topological sorting 2. connected component 3. find cycle。同时要注意有向/无向的区别。

Topological sorting

Toplogical sorting一定是针对有向图,常见的做法是

  • DFS遍历有向图,最早被遍历完的是需要排到最后面的,时间复杂度O(m+n)
    • 设置三种遍历时的状态,VISITED = 1, UNVISITED = 0, VISITING = -1
    • DFS每一个node,如果:
      • node的状态是UNVISITED: 将状态变为VISITING,然后DFS所有它指向的node,所有指向的node都遍历完以后再变为VISITED,加入到结果中
      • node的状态是VISITED: 说明这个node已经加入到结果中了,直接返回
      • node的状态是VISITING: 证明图中有环
      • 注意: DFS的结果是最后执行的先加入到结果中去
  • BFS,通过判断indegree数量,indegree先为零的先拍在前面,时间复杂度O(n+m)
    • 新建一个数组,数组中储存所有node的indegree数量
    • 遍历数组,找到所有的source node(indegree为0)加入到Queue中
    • 依次从Queue中弹出node加入到排序结果中,对于该node指向的nodes,它们的indegree减1,同时判断它们是不是indegree变成0,是的话加入Queue
    • 最终如果排序结果的长度与原长度相等,证明所有node都参与了排序,不存在环,否则证明有环
  1. Course Schedule: topological sorting的代表性题目,可以使用DFS和BFS来做,这道题本质上是检测有没有环,所以我们可以看出来,进行Toplogical sort的前提就是是DAG(无环)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    class Solution {
    //DFS
    private final int UNVISITED = 0;
    private final int VISITED = 1;
    private final int VISITING = -1;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
    //construct graph
    List<List<Integer>> al = new LinkedList();
    for(int i =0;i<=numCourses-1;i++){
    al.add(new LinkedList<>());
    }
    for(int[] pre:prerequisites){
    int start = pre[1];
    int end = pre[0];
    al.get(start).add(end);
    }

    //do dfs
    int[] visited = new int[numCourses];
    for(int i =0;i<=numCourses-1;i++){
    if(dfs(i, al, visited)==false){
    return false;
    }
    }

    return true;
    }

    private boolean dfs(int node, List<List<Integer>> al, int[] visited){
    if(visited[node]==VISITED){
    return true;
    }
    else if(visited[node]==VISITING){
    return false;
    }
    else {
    visited[node] = VISITING;
    for(int i:al.get(node)){
    if(dfs(i, al, visited)==false){
    return false;
    }
    }
    visited[node] = VISITED;
    return true;
    }

    }
    }
  2. Course Schedule II: topological sorting的代表性题目,可以使用DFS和BFS来做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    class Solution {
    // BFS
    public int[] findOrder(int numCourses, int[][] prerequisites) {
    // construct graph and indegree count
    List<List<Integer>> al = new LinkedList<>();
    int[] indegree = new int[numCourses];
    for(int i = 0;i<=numCourses-1;i++){
    al.add(new LinkedList<>());
    }

    for(int[] pre:prerequisites){
    int start = pre[1];
    int end = pre[0];
    al.get(start).add(end);
    indegree[end]++;
    }

    Queue<Integer> q = new LinkedList<>();
    for(int i =0;i<=numCourses-1 ;i++){
    if(indegree[i]==0){
    q.offer(i);
    }
    }

    List<Integer> ans = new LinkedList<>();
    while(!q.isEmpty()){
    int node = q.poll();
    ans.add(node);
    for(int j:al.get(node)){
    indegree[j]--;
    if(indegree[j]==0){
    q.offer(j);
    }
    }
    }

    int[] res = new int[ans.size()];
    for(int i =0;i<=ans.size()-1;i++){
    res[i] = ans.get(i);
    }

    if(res.length==numCourses){
    return res;
    }
    else {
    return new int[0];
    }


    }
    }
  3. Alien Dictionary: 字母的前后顺序关系是一种有向图,又由于题中说可能有多种结果,所以使用topological sort

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    class Solution {
    public String alienOrder(String[] words) {
    Map<Character, Set<Character>> map = new HashMap<>();//这里必须用set,因为针对相同的edge如果重复计入会导致indegree问题
    Map<Character, Integer> indegree = new HashMap<>();
    for(String s: words){
    for(char c: s.toCharArray()){
    indegree.put(c,0);//所有字母都要有indegree
    map.put(c, new HashSet<>());//所有字母都要有map
    }
    }
    for(int i =1;i<=words.length-1;i++){
    String s1 = words[i-1];
    String s2 = words[i];
    for(int j = 0;j<=s1.length()-1;j++){
    char c1 = s1.charAt(j);
    char c2 = s2.charAt(j);
    if(c1!=c2){
    Set<Character> l = map.getOrDefault(c1, new HashSet<>());
    if(!l.contains(c2)){//非常重要,使用set判断是不是已经有了这个edge,以此来保证indegree不会出错
    l.add(c2);
    map.put(c1,l);
    indegree.put(c2, indegree.getOrDefault(c2, 0)+1);
    }

    break;
    }
    }
    }

    String ans = "";
    Queue<Character> q = new LinkedList<>();
    for(Map.Entry<Character, Integer> e: indegree.entrySet()){
    char key = e.getKey();
    int value = e.getValue();
    if(value==0){
    q.offer(key);
    }
    }

    while(!q.isEmpty()){
    char c= q.poll();
    ans = ans + c;
    for(char ch:map.get(c)){
    indegree.put(ch, indegree.get(ch)-1);
    if(indegree.get(ch)==0){
    q.offer(ch);
    }
    }
    }

    if(ans.length()!=indegree.size()){
    return "";
    }
    else {
    return ans;
    }
    }
    }

Connect Component

找connected components (dfs, bfs, union find)。对于找无向图的connected component还是很简单的,无非是前面提到的三种方法,如果对于有向图要寻找SCC(Strong Connected Component)则比较复杂。
三种方法的时间复杂度都是O(m+n)
200. Number of Islands:可以把矩阵抽象成graph,这道题可以不用设置visited[]数组,直接改变grid的值即可(visited过的变成0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
//DFS
public int numIslands(char[][] grid) {
/*
11110 1-1-1-1 0
| | |
1-1 0 1 0
11010
11000
00000

visited[][]
*/
int m = grid.length;
if(m==0)
return 0;
int n = grid[0].length;
int ans = 0;
boolean[][] visited = new boolean[m][n];
for(int i =0;i<=m-1;i++){
for(int j =0;j<=n-1;j++){
if(visited[i][j]==false && grid[i][j]=='1'){
DFS(grid, visited, i, j);
ans++;
}
}
}

return ans;

}

private void DFS(char[][] grid, boolean[][] visited, int i, int j){
if(!(i<=grid.length-1 && i>=0 && j<=grid[0].length-1 && j>=0)){
return;
}
if(visited[i][j]==true || grid[i][j]=='0'){
return;
}



visited[i][j] = true;
DFS(grid, visited, i+1, j);
DFS(grid, visited, i-1, j);
DFS(grid, visited, i, j+1);
DFS(grid, visited, i, j-1);

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Union Find
class Solution {
class UnionFind {
private int[] parent;
private int[] rank;
public int count;
public UnionFind(char[][] grid){
int m = grid.length;
int n = grid[0].length;
parent = new int[m*n];
rank = new int[m*n];
for(int i = 0;i<=m-1;i++){
for(int j =0;j<=n-1;j++){
int index = j*m+i;
if(grid[i][j]=='1'){
parent[index] = index;
rank[index] = 0;
count++;
}
}
}
}

public int find(int i){
if(parent[i] == i){
return i;
}
else {
parent[i] = find(parent[i]);
return parent[i];
}

}

public void union(int i , int j){
int indexi = find(i);
int indexj = find(j);
if(indexi!=indexj){
if(rank[indexi]>rank[indexj]){
parent[indexj] = indexi;
}
else if(rank[indexi]<rank[indexj]){
parent[indexi] = indexj;
}
else {
parent[indexi] = indexj;
rank[indexj]++;
}
count--;
}
}
}
public int numIslands(char[][] grid) {
int m = grid.length;
if(m==0)
return 0;
int n = grid[0].length;

UnionFind uf = new UnionFind(grid);
for(int i =0;i<=m-1;i++){
for(int j =0;j<=n-1;j++){
int index = j*m+i;
if(grid[i][j]=='1'){
grid[i][j] = '0';
if(i+1<=m-1 && grid[i+1][j]=='1'){
uf.union(index, j*m+i+1);
}
if(j+1<=n-1 && grid[i][j+1]=='1'){
uf.union(index, (j+1)*m+i);
}

}
}
}

return uf.count;

}
}
  1. Number of Connected Components in an Undirected Graph
  2. Friend Circles
    都是无向图

图里是否有环

Bipartite

Bipartite的定义是一个图可以分为两个集合,这个图中所有的edge连接的vertice必须分别在两个集合
解法是分别使用DFS或者BFS为两个集合染色,一个集合红色,一个集合蓝色,如果某个点出现两个色证明不是Bipartite

BFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
785. Is Graph Bipartite?
Medium

871

116

Favorite

Share
Given an undirected graph, return true if and only if it is bipartite.

Recall that a graph is bipartite if we can split it's set of nodes into two independent subsets A and B such that every edge in the graph has one node in A and another node in B.

The graph is given in the following form: graph[i] is a list of indexes j for which the edge between nodes i and j exists. Each node is an integer between 0 and graph.length - 1. There are no self edges or parallel edges: graph[i] does not contain i, and it doesn't contain any element twice.

Example 1:
Input: [[1,3], [0,2], [1,3], [0,2]]
Output: true
Explanation:
The graph looks like this:
0----1
| |
| |
3----2
We can divide the vertices into two groups: {0, 2} and {1, 3}.
Example 2:
Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
Output: false
Explanation:
The graph looks like this:
0----1
| \ |
| \ |
3----2
We cannot find a way to divide the set of nodes into two independent subsets.


Note:

graph will have length in range [1, 100].
graph[i] will contain integers in range [0, graph.length - 1].
graph[i] will not contain i or duplicate values.
The graph is undirected: if any element j is in graph[i], then i will be in graph[j].
*/

class Solution {
public boolean isBipartite(int[][] graph) {
int n = graph.length;
int[] colors = new int[n];
Queue<Integer> queue = new LinkedList<>();
for(int i = 0;i<=n-1;i++){//所有node都要经过一次queue,因为会有多个connected component的情况
if(colors[i]==0){
colors[i] = 1;
queue.offer(i);
while(!queue.isEmpty()){
int node = queue.poll();
int color = colors[node];
for(int neighbor:graph[node]){
if(colors[neighbor]==0){
colors[neighbor] = -color;
queue.offer(neighbor);
}
else if(colors[neighbor]==color){
return false;
}
}
}
}
}
return true;

}
}

DFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public boolean isBipartite(int[][] graph) {
int n = graph.length;
int[] colors = new int[n];
for(int i = 0;i<=n-1;i++){
if(colors[i]==0){
if(!valid(graph, colors, i, 1)){
return false;
}
}
}
return true;
}

private boolean valid(int[][] graph, int[] colors, int i, int color){
if(colors[i]==-color){
return false;
}
else if(colors[i]==color){
return true;
}
else {
colors[i] = color;
for(int neighbor:graph[i]){
if(!valid(graph, colors, neighbor, -color)){
return false;
}
}
return true;
}
}
}