二分图
如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。
785 判断是否为二分图
着色问题用深度遍历的方法借助栈非循环进行,考虑是非连通情况所以要对图中的每一个节点都进行遍历。
public boolean isBipartite(int[][] graph) {
if (graph == null || graph.length == 0) return false;
int v = graph.length;
int[] colors = new int[v]; // 0未被染色, 1黑 2白
// 要考虑非连通图, 所以要遍历每一个结点
for (int i = 0; i < v; i++) {
// lastColor为0
if (!dfs(graph, i, colors, 0)) return false;
}
return true;
}
private boolean dfs(int[][] graph, int i, int[] colors, int lastColor) {
// 注意,被染色的就不要继续染色了(因为这是自底向上的,被染色的点,其相连的节点肯定被染色了)
// 如果继续对被染色的节点染色,就会导致死循环
if (colors[i] != 0) return colors[i] != lastColor;
// 未被染色,染成与相邻结点不同的颜色(lastColor为0时,就染成1)
colors[i] = lastColor == 1 ? 2 : 1;
for (int j = 0; j < graph[i].length; j++) {
if (!dfs(graph, graph[i][j], colors, colors[i])) return false;
}
return true;
}
借用栈进行该过程
class Solution { public boolean isBipartite(int[][] graph) { int n = graph.length; int[] color = new int[n]; Arrays.fill(color, -1);
for (int start = 0; start < n; ++start) { if (color[start] == -1) { Stack<Integer> stack = new Stack(); stack.push(start); color[start] = 0;
while (!stack.empty()) { Integer node = stack.pop(); for (int nei: graph[node]) { if (color[nei] == -1) { stack.push(nei); color[nei] = color[node] ^ 1; } else if (color[nei] == color[node]) { return false; } } } } }
return true; }}
拓扑排序
207 课程安排的合法性
图的邻接表和邻接矩阵表示方法
DFS BFS
拓扑排序
该题只需要判断在有向图中是否存在环
详细讲解
使用拓扑排序方法 广度优先角度
class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { int[] dp=new int[numCourses]; for(int[] pre:prerequisites) dp[pre[0]]++; LinkedList<Integer> queue=new LinkedList<>(); for(int i=0;i<numCourses;i++){ if(dp[i]==0) queue.addLast(i); } while(!queue.isEmpty()){ Integer flag=queue.removeFirst(); numCourses--; for(int[] pre:prerequisites){ if(pre[1]!=flag) continue; if(--dp[pre[0]]==0) queue.add(pre[0]); } } return numCourses==0; }}
深度优先解决
需要用标记位来表示是否被访问过
有三种状态:未被访问过
被当前元素访问过
被其他元素访问过
当遇到的元素未被访问过,直接标记已被当前元素访问过。
当遇到元素被其他元素访问过,则直接证明无环。
当遇到哦元素被当前元素访问过,则直接返回有环。
可以用三个值来分别标记当前的状态,也可以使用两个状态量,一个用来标记当前元素访问情况,一个用来标记其他元素访问情况。
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer>[] graphic = new List[numCourses];
for (int i = 0; i < numCourses; i++) {
graphic[i] = new ArrayList<>();
}
for (int[] pre : prerequisites) {
graphic[pre[0]].add(pre[1]);
}
boolean[] globalMarked = new boolean[numCourses];
boolean[] localMarked = new boolean[numCourses];
for (int i = 0; i < numCourses; i++) {
if (hasCycle(globalMarked, localMarked, graphic, i)) {
return false;
}
}
return true;
}
private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked,
List<Integer>[] graphic, int curNode) {
if (localMarked[curNode]) {
return true;
}
if (globalMarked[curNode]) {
return false;
}
globalMarked[curNode] = true;
localMarked[curNode] = true;
for (int nextNode : graphic[curNode]) {
if (hasCycle(globalMarked, localMarked, graphic, nextNode)) {
return true;
}
}
localMarked[curNode] = false;
return false;
}
注意标志量的访问顺序。
210 课程安排顺序
用一个栈结构对数据进行记录
public int[] findOrder(int numCourses, int[][] prerequisites) {
List<Integer>[] graphic = new List[numCourses];
for (int i = 0; i < numCourses; i++) {
graphic[i] = new ArrayList<>();
}
for (int[] pre : prerequisites) {
graphic[pre[0]].add(pre[1]);
}
Stack<Integer> postOrder = new Stack<>();
boolean[] globalMarked = new boolean[numCourses];
boolean[] localMarked = new boolean[numCourses];
for (int i = 0; i < numCourses; i++) {
if (hasCycle(globalMarked, localMarked, graphic, i, postOrder)) {
return new int[0];
}
}
int[] orders = new int[numCourses];
for (int i = numCourses - 1; i >= 0; i--) {
orders[i] = postOrder.pop();
}
return orders;
}
private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graphic,
int curNode, Stack<Integer> postOrder) {
if (localMarked[curNode]) {
return true;
}
if (globalMarked[curNode]) {
return false;
}
globalMarked[curNode] = true;
localMarked[curNode] = true;
for (int nextNode : graphic[curNode]) {
if (hasCycle(globalMarked, localMarked, graphic, nextNode, postOrder)) {
return true;
}
}
localMarked[curNode] = false;
postOrder.push(curNode);
return false;
}
并查集
684 冗余连接
并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。
算法思想理解
遍历所有的边将连通的点放到同一集合,构成连通分量。在遍历过程中如果某一条边的两个点属于同一连通分量,那么该边是冗余的。
public int[] findRedundantConnection(int[][] edges) {
int N = edges.length;
UF uf = new UF(N);
for (int[] e : edges) {
int u = e[0], v = e[1];
if (uf.connect(u, v)) {
return e;
}
uf.union(u, v);
}
return new int[]{-1, -1};
}
private class UF {
private int[] id;
UF(int N) {
id = new int[N + 1];
for (int i = 0; i < id.length; i++) {
id[i] = i;
}
}
void union(int u, int v) {
int uID = find(u);
int vID = find(v);
if (uID == vID) {
return;
}
for (int i = 0; i < id.length; i++) {
if (id[i] == uID) {
id[i] = vID;
}
}
}
int find(int p) {
return id[p];
}
boolean connect(int u, int v) {
return find(u) == find(v);
}
}
部分解答思路参考:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E5%9B%BE.md