2019-04-21 10:36:08 +08:00
|
|
|
|
<!-- GFM-TOC -->
|
2019-03-27 20:57:37 +08:00
|
|
|
|
* [二分图](#二分图)
|
2019-05-14 22:56:30 +08:00
|
|
|
|
* [1. 判断是否为二分图](#1-判断是否为二分图)
|
2019-03-27 20:57:37 +08:00
|
|
|
|
* [拓扑排序](#拓扑排序)
|
2019-05-14 22:56:30 +08:00
|
|
|
|
* [1. 课程安排的合法性](#1-课程安排的合法性)
|
|
|
|
|
* [2. 课程安排的顺序](#2-课程安排的顺序)
|
2019-03-27 20:57:37 +08:00
|
|
|
|
* [并查集](#并查集)
|
2019-05-14 22:56:30 +08:00
|
|
|
|
* [1. 冗余连接](#1-冗余连接)
|
2019-04-21 10:36:08 +08:00
|
|
|
|
<!-- GFM-TOC -->
|
2019-03-27 20:57:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 二分图
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。
|
|
|
|
|
|
2019-05-14 22:56:30 +08:00
|
|
|
|
## 1. 判断是否为二分图
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
[785. Is Graph Bipartite? (Medium)](https://leetcode.com/problems/is-graph-bipartite/description/)
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
Input: [[1,3], [0,2], [1,3], [0,2]]
|
|
|
|
|
Output: true
|
2019-03-08 20:31:07 +08:00
|
|
|
|
Explanation:
|
2019-03-27 20:57:37 +08:00
|
|
|
|
The graph looks like this:
|
2019-03-08 20:31:07 +08:00
|
|
|
|
0----1
|
2019-03-27 20:57:37 +08:00
|
|
|
|
| |
|
|
|
|
|
| |
|
2019-03-08 20:31:07 +08:00
|
|
|
|
3----2
|
2019-03-27 20:57:37 +08:00
|
|
|
|
We can divide the vertices into two groups: {0, 2} and {1, 3}.
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
Example 2:
|
|
|
|
|
Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
|
|
|
|
|
Output: false
|
2019-03-08 20:31:07 +08:00
|
|
|
|
Explanation:
|
2019-03-27 20:57:37 +08:00
|
|
|
|
The graph looks like this:
|
2019-03-08 20:31:07 +08:00
|
|
|
|
0----1
|
2019-03-27 20:57:37 +08:00
|
|
|
|
| \ |
|
|
|
|
|
| \ |
|
2019-03-08 20:31:07 +08:00
|
|
|
|
3----2
|
2019-03-27 20:57:37 +08:00
|
|
|
|
We cannot find a way to divide the set of nodes into two independent subsets.
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```java
|
2019-03-27 20:57:37 +08:00
|
|
|
|
public boolean isBipartite(int[][] graph) {
|
|
|
|
|
int[] colors = new int[graph.length];
|
|
|
|
|
Arrays.fill(colors, -1);
|
|
|
|
|
for (int i = 0; i < graph.length; i++) { // 处理图不是连通的情况
|
|
|
|
|
if (colors[i] == -1 && !isBipartite(i, 0, colors, graph)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] graph) {
|
|
|
|
|
if (colors[curNode] != -1) {
|
|
|
|
|
return colors[curNode] == curColor;
|
|
|
|
|
}
|
|
|
|
|
colors[curNode] = curColor;
|
|
|
|
|
for (int nextNode : graph[curNode]) {
|
|
|
|
|
if (!isBipartite(nextNode, 1 - curColor, colors, graph)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
# 拓扑排序
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
常用于在具有先序关系的任务规划中。
|
|
|
|
|
|
2019-05-14 22:56:30 +08:00
|
|
|
|
## 1. 课程安排的合法性
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
[207. Course Schedule (Medium)](https://leetcode.com/problems/course-schedule/description/)
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
2, [[1,0]]
|
|
|
|
|
return true
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
2, [[1,0],[0,1]]
|
|
|
|
|
return false
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
题目描述:一个课程可能会先修课程,判断给定的先修课程规定是否合法。
|
|
|
|
|
|
|
|
|
|
本题不需要使用拓扑排序,只需要检测有向图是否存在环即可。
|
|
|
|
|
|
|
|
|
|
```java
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-05-14 22:56:30 +08:00
|
|
|
|
## 2. 课程安排的顺序
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
[210. Course Schedule II (Medium)](https://leetcode.com/problems/course-schedule-ii/description/)
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
4, [[1,0],[2,0],[3,1],[3,2]]
|
|
|
|
|
There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
使用 DFS 来实现拓扑排序,使用一个栈存储后序遍历结果,这个栈的逆序结果就是拓扑排序结果。
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
证明:对于任何先序关系:v->w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
```java
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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;
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
# 并查集
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。
|
|
|
|
|
|
2019-05-14 22:56:30 +08:00
|
|
|
|
## 1. 冗余连接
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
[684. Redundant Connection (Medium)](https://leetcode.com/problems/redundant-connection/description/)
|
2019-03-08 20:31:07 +08:00
|
|
|
|
|
|
|
|
|
```html
|
2019-03-27 20:57:37 +08:00
|
|
|
|
Input: [[1,2], [1,3], [2,3]]
|
|
|
|
|
Output: [2,3]
|
|
|
|
|
Explanation: The given undirected graph will be like this:
|
|
|
|
|
1
|
|
|
|
|
/ \
|
|
|
|
|
2 - 3
|
2019-03-08 20:31:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
题目描述:有一系列的边连成的图,找出一条边,移除它之后该图能够成为一棵树。
|
|
|
|
|
|
|
|
|
|
```java
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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};
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-03-27 20:57:37 +08:00
|
|
|
|
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);
|
|
|
|
|
}
|
2019-03-08 20:31:07 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
2019-03-27 20:57:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-06-08 11:54:07 +08:00
|
|
|
|
</br><div align="center">💡 </br></br> 更多精彩内容将发布在公众号 **CyC2018**, 后台回复 “离线下载” 以及 “复习大纲” 可领取学习资料。😊
|
2019-06-08 11:44:52 +08:00
|
|
|
|
<div align="center"><img width="450px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/other/公众号海报.png"></img></div>
|