欧拉图
大约 3 分钟
欧拉图
- OI Wiki: https://oi-wiki.org/graph/euler/
题目 | 难度 | |
---|---|---|
332. 重新安排行程 | 困难 | |
753. 破解保险箱 | 困难 | |
2097. 合法重新排列数对 | 困难 | T4 |
定义
通过图中所有边恰好一次的通路称为欧拉通路。
通过图中所有边恰好一次的回路称为欧拉回路。
具有欧拉回路的无向图或有向图称为欧拉图。
Hierholzer 算法
332. 重新安排行程
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class Solution332 {
// 如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
private Map<String, PriorityQueue<String>> adj;
private List<String> resList;
public List<String> findItinerary(List<List<String>> tickets) {
adj = new HashMap<>();
for (List<String> ticket : tickets) {
String u = ticket.get(0);
String v = ticket.get(1);
adj.computeIfAbsent(u, key -> new PriorityQueue<>()).add(v);
}
resList = new ArrayList<>();
// 欧拉通路 / 欧拉回路
// 该行程必须从 JFK 开始
dfs("JFK");
Collections.reverse(resList);
return resList;
}
private void dfs(String u) {
// 当我们遍历完一个节点所连的所有节点后,我们才将该节点入队(即逆序入队)
while (adj.containsKey(u) && adj.get(u).size() > 0) {
String v = adj.get(u).poll();
dfs(v);
}
resList.add(u);
}
}
753. 破解保险箱
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
public class Solution753 {
private StringBuilder ans;
private Map<String, Queue<String[]>> adj;
// 假设 k = 4, n = 3 时,节点分别为 00, 01, 02, 03, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33,
// 每个节点的出边的编号分别为 0, 1, 2, 3,
// 那么 00 和它的出边形成了 000, 001, 002, 003 这 4 个 3 位数,32 和它的出边形成了 320, 321, 322, 323 这 4 个 3 位数
// 形成一个有向图,16 个节点,64 条边,每个节点 4 条出边,4 条入边
public String crackSafe(int n, int k) {
ans = new StringBuilder();
// 0,1,···,k-1
String[] ss = new String[k];
String[] permute = {""};
for (int i = 0; i < k; i++) {
ss[i] = String.valueOf(i);
}
for (int i = 0; i < n - 1; i++) {
permute = cartesianProduct(permute, ss);
}
// 建图
adj = new HashMap<>();
for (String u : permute) {
for (int x = 0; x < k; x++) {
String v = (u + x).substring(1);
String w = String.valueOf(x);
adj.computeIfAbsent(u, key -> new LinkedList<>()).add(new String[]{v, w});
}
}
// 欧拉回路
dfs(permute[0]);
return ans.toString() + permute[0];
}
private void dfs(String u) {
while (adj.containsKey(u) && adj.get(u).size() > 0) {
String[] v = adj.get(u).remove();
dfs(v[0]);
ans.append(v[1]);
}
}
// 笛卡尔积
private String[] cartesianProduct(String[] arr1, String[] arr2) {
int len = arr1.length * arr2.length;
String[] res = new String[len];
int id = 0;
for (String s1 : arr1) {
for (String s2 : arr2) {
res[id++] = s1 + s2;
}
}
return res;
}
private int k;
private Set<Integer> seen;
private StringBuilder stringBuilder;
private int highest;
// 官方解
public String crackSafe2(int n, int k) {
this.k = k;
seen = new HashSet<>();
stringBuilder = new StringBuilder();
// 10^(n-1)
highest = (int) Math.pow(10, n - 1);
dfs(0);
stringBuilder.append("0".repeat(n - 1));
return stringBuilder.toString();
}
private void dfs(int u) {
for (int x = 0; x < k; x++) {
int v = u * 10 + x;
if (!seen.contains(v)) {
seen.add(v);
dfs(v % highest);
stringBuilder.append(x);
}
}
}
}
2097. 合法重新排列数对
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
public class Solution2097 {
private Map<Integer, Queue<Integer>> adj;
private List<int[]> resList;
public int[][] validArrangement(int[][] pairs) {
Map<Integer, Integer> degreeMap = new HashMap<>();
adj = new HashMap<>();
for (int[] pair : pairs) {
int u = pair[0];
int v = pair[1];
adj.computeIfAbsent(u, key -> new LinkedList<>()).add(v);
// 出度 -1 入度 +1
degreeMap.put(u, degreeMap.getOrDefault(u, 0) - 1);
degreeMap.put(v, degreeMap.getOrDefault(v, 0) + 1);
}
resList = new ArrayList<>();
// 欧拉通路
// 恰有一个顶点 出度 = 入度 + 1(起点) && 恰有一个顶点 入度 = 出度 + 1(终点)
// dfs 起点
for (Map.Entry<Integer, Integer> entry : degreeMap.entrySet()) {
if (entry.getValue() == -1) {
dfs(entry.getKey());
Collections.reverse(resList);
return resList.toArray(int[][]::new);
}
}
// 欧拉回路
// 每个顶点的入度和出度相同
// dfs 任意点
dfs(pairs[0][0]);
Collections.reverse(resList);
return resList.toArray(int[][]::new);
}
private void dfs(int u) {
// 当我们遍历完一个节点所连的所有节点后,我们才将该节点入队(即逆序入队)
while (adj.containsKey(u) && adj.get(u).size() > 0) {
int v = adj.get(u).remove();
dfs(v);
resList.add(new int[]{u, v});
}
}
}
(全文完)