leetcode图的相关题目解析:
对图的遍历就是两个经典的方法DFS和BFS。BFS经常用Queue实现,DFS经常用递归实现(可改为栈实现)。
1、Clone Graph
题意:给定图中一个节点,克隆图
思路:用HashMap,key存lable值,value存新clone的node,用BFS方法遍历帮助拷贝neighbors的值。给定给一个节点,若lable在map中有,则表示已经clone;若没有,则将当前节点加入map中,一次clone节点的邻居节点。
代码:
class UndirectedGraphNode {
int label;
List<UndirectedGraphNode> neighbors;
UndirectedGraphNode(int x) {
label = x;
neighbors = new ArrayList<UndirectedGraphNode>();
}
}
private HashMap<Integer, UndirectedGraphNode> map = new HashMap<>();
public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
return clone(node);
}
private UndirectedGraphNode clone(UndirectedGraphNode node) {
if (node == null) return null;
if (map.containsKey(node.label)) {
return map.get(node.label);
}
UndirectedGraphNode clone = new UndirectedGraphNode(node.label);
map.put(clone.label, clone);
for (UndirectedGraphNode neighbor : node.neighbors) {
clone.neighbors.add(clone(neighbor));
}
return clone;
}
2、Evaluate Division
题意:以算式A / B = k的形式给出若干等式,其中A和B是以字符串表示的变量,k是实数(浮点数)。给定一些查询,返回结果。如果答案不存在,返回 -1.0
Given a / b = 2.0, b / c = 3.0.
queries are: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? .
return [6.0, 0.5, -1.0, 1.0, -1.0 ].
思路:输入等式可以看做一个有向图,例如等式a / b = 2.0,可以转化为两条边:
public double[] calcEquation(String[][] equations, double[] values, String[][] queries) {
// build graph, use adjacent list
map = new HashMap();
for(int i = 0; i < equations.length; i++) {
String[] equation = equations[i];
if(!map.containsKey(equation[0])) map.put(equation[0], new ArrayList());
map.get(equation[0]).add(new Info(equation[1], values[i]));
if(!map.containsKey(equation[1])) map.put(equation[1], new ArrayList());
map.get(equation[1]).add(new Info(equation[0], 1 / values[i]));
}
double[] result = new double[queries.length];
for(int i = 0; i < result.length; i++) {
result[i] = find(queries[i][0], queries[i][1], 1, new HashSet());
}
return result;
}
HashMap<String, List<Info>> map;
private double find(String start, String end, double value, Set<String> visited) {
if(visited.contains(start)) return -1;
if(!map.containsKey(start)) return -1;
if(start.equals(end)) return value;
visited.add(start);
for(Info next : map.get(start)) {
double sub = find(next.den, end, value * next.val, visited);
if(sub != -1.0) return sub;
}
visited.remove(start);
return -1;
}
class Info {
String den;
double val;
Info(String den, double val) { this.den = den; this.val = val; }
}
3、题目:Reconstruct Itinerary
题意:tickets = [[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
Return [“JFK”, “MUC”, “LHR”, “SFO”, “SJC”].
思路:存储在hash中,并且使用PriorityQueue来排序。等我们图建立好了以后,从节点JFK开始遍历,只要当前节点映射的multiset里有节点,我们取出这个节点,将其在multiset里删掉,然后继续递归遍历这个节点,由于题目中限定了一定会有解,那么等图中所有的multiset中都没有节点的时候,我们把当前节点存入结果中(头插形式),然后再一层层回溯回去。
public List<String> findItinerary(String[][] tickets) {
for (String[] ticket : tickets)
targets.computeIfAbsent(ticket[0], k -> new PriorityQueue()).add(ticket[1]);
visit("JFK");
return route;
}
Map<String, PriorityQueue<String>> targets = new HashMap<>();
List<String> route = new LinkedList();
void visit(String airport) {
while(targets.containsKey(airport) && !targets.get(airport).isEmpty())
visit(targets.get(airport).poll());
route.add(0, airport);
}
4、Minimum Height Trees
题意:给定一系列边,寻找使得树高度最小的根节点
思路:从叶节点开始寻找,寻找离所有叶节点最远的节点,则为根。每次去掉当前图的所有叶子节点,重复此操作直到只剩下最后的根。
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if (n == 1) return Collections.singletonList(0);
//构建邻接表
List<Set<Integer>> adj = new ArrayList<>(n);
for (int i = 0; i < n; ++i) adj.add(new HashSet<>());
for (int[] edge : edges) {
adj.get(edge[0]).add(edge[1]);
adj.get(edge[1]).add(edge[0]);
}
//找到所有叶子结点,所有入度(即相连边数)为 1 的节点即是叶子节点。
List<Integer> leaves = new ArrayList<>();
for (int i = 0; i < n; ++i)
if (adj.get(i).size() == 1) leaves.add(i);
//找高度最小的节点,即找离所有叶子节点最远的节点,也即找最中心的节点。
while (n > 2) {
n -= leaves.size();
List<Integer> newLeaves = new ArrayList<>();
for (int i : leaves) {
//每次去掉当前图的所有叶子节点,重复此操作直到只剩下最后的根。
int j = adj.get(i).iterator().next();
adj.get(j).remove(i);
if (adj.get(j).size() == 1) newLeaves.add(j);
}
leaves = newLeaves;
}
return leaves;
}
5、Course Schedule
题意:2, [[1,0],[0,1]],代表有两个课程,0的先行课是1,1的先行课的是0,不可能,输出false。
翻译:在一个有向图中,每次找到一个没有前驱节点的节点(也就是入度为0的节点),然后把它指向其他节点的边都去掉,重复这个过程(BFS),直到所有节点已被找到,或者没有符合条件的节点(如果图中有环存在)。
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[][] matrix = new int[numCourses][numCourses]; // i -> j
int[] indegree = new int[numCourses];
for (int i=0; i<prerequisites.length; i++) {
int ready = prerequisites[i][0];
int pre = prerequisites[i][1];
if (matrix[pre][ready] == 0)
indegree[ready]++; //duplicate case
matrix[pre][ready] = 1;
}
int count = 0;
Queue<Integer> queue = new LinkedList();
for (int i=0; i<indegree.length; i++) {
if (indegree[i] == 0) queue.offer(i);
}
while (!queue.isEmpty()) {
int course = queue.poll();
count++;
for (int i=0; i<numCourses; i++) {
if (matrix[course][i] != 0) {
if (--indegree[i] == 0)
queue.offer(i);
}
}
}
return count == numCourses;
}