Open xiedaxia1hao opened 3 years ago
Given a directed graph, find the topological ordering of its vertices.
Input: Vertices=5, Edges=[4, 2], [4, 3], [2, 0], [2, 1], [3, 1] Output: Following are all valid topological sorts for the given graph: 1) 4, 2, 3, 0, 1 2) 4, 3, 2, 0, 1 3) 4, 3, 2, 1, 0 4) 4, 2, 3, 1, 0 5) 4, 2, 0, 3, 1
这种题就是topological sort的模版题,一般解拓扑排序的题,我们用以下的套路来:
具体的代码实现如下:
import java.util.*;
class TopologicalSort {
public static List<Integer> sort(int vertices, int[][] edges) {
List<Integer> sortedOrder = new ArrayList<>();
if (vertices <= 0)
return sortedOrder;
// a. Initialize the graph
HashMap<Integer, Integer> inDegree = new HashMap<>(); // count of incoming edges for every vertex
HashMap<Integer, List<Integer>> graph = new HashMap<>(); // adjacency list graph
for (int i = 0; i < vertices; i++) {
inDegree.put(i, 0);
graph.put(i, new ArrayList<Integer>());
}
// b. Build the graph
for (int i = 0; i < edges.length; i++) {
int parent = edges[i][0], child = edges[i][1];
graph.get(parent).add(child); // put the child into it's parent's list
inDegree.put(child, inDegree.get(child) + 1); // increment child's inDegree
}
// c. Find all sources i.e., all vertices with 0 in-degrees
Queue<Integer> sources = new LinkedList<>();
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0)
sources.add(entry.getKey());
}
// d. For each source, add it to the sortedOrder and subtract one from all of its children's in-degrees
// if a child's in-degree becomes zero, add it to the sources queue
while (!sources.isEmpty()) {
int vertex = sources.poll();
sortedOrder.add(vertex);
List<Integer> children = graph.get(vertex); // get the node's children to decrement their in-degrees
for (int child : children) {
inDegree.put(child, inDegree.get(child) - 1);
if (inDegree.get(child) == 0)
sources.add(child);
}
}
if (sortedOrder.size() != vertices) // topological sort is not possible as the graph has a cycle
return new ArrayList<>();
return sortedOrder;
}
public static void main(String[] args) {
List<Integer> result = TopologicalSort.sort(4,
new int[][] { new int[] { 3, 2 }, new int[] { 3, 0 }, new int[] { 2, 0 }, new int[] { 2, 1 } });
System.out.println(result);
result = TopologicalSort.sort(5, new int[][] { new int[] { 4, 2 }, new int[] { 4, 3 }, new int[] { 2, 0 },
new int[] { 2, 1 }, new int[] { 3, 1 } });
System.out.println(result);
result = TopologicalSort.sort(7, new int[][] { new int[] { 6, 4 }, new int[] { 6, 2 }, new int[] { 5, 3 },
new int[] { 5, 4 }, new int[] { 3, 0 }, new int[] { 3, 1 }, new int[] { 3, 2 }, new int[] { 4, 1 } });
System.out.println(result);
}
}
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。 Example 1: Input: numCourses = 2, prerequisites = [[1,0]] Output: true Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible. Example 2: Input: numCourses = 2, prerequisites = [[1,0],[0,1]] Output: false Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
这题就是一个leetcode上的模版题,直接套模版就好了。
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<Integer> sortedList = new ArrayList<>();
if(numCourses <= 0) {
return false;
}
// initialization
Map<Integer, Integer> inDegree = new HashMap<>();
Map<Integer, List<Integer>> graph = new HashMap<>();
for(int i = 0; i < numCourses; i++) {
inDegree.put(i, 0);
graph.put(i, new ArrayList<>());
}
// build the graph
for(int[] prerequisite : prerequisites) {
int parent = prerequisite[0], child = prerequisite[1];
inDegree.put(child, inDegree.get(child) + 1);
graph.get(parent).add(child);
}
//find sources
Queue<Integer> sources = new LinkedList<>();
for(Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if(entry.getValue() == 0) {
sources.add(entry.getKey());
}
}
// sort
while(!sources.isEmpty()) {
int vertex = sources.poll();
sortedList.add(vertex);
List<Integer> children = graph.get(vertex);
for(int child : children) {
inDegree.put(child, inDegree.get(child)-1);
if(inDegree.get(child) == 0) {
sources.add(child);
}
}
}
return sortedList.size() == numCourses;
}
}
这一题和上面一题基本一致,我们只要在最后把上课的顺序打印出来即可,即打印sortedList
。
Grokking. All Tasks Scheduling Orders
前两题只是基本的套路题,我们只是1)判断该DAG是否存在一个线形排序,或者2)输出该DAG的其中一个线形排序。
那我们怎样来输出该DAG的所有线形排序呢?
这个时候就要用到backtrack了。
import java.util.*;
class AllTaskSchedulingOrders {
public static void printOrders(int tasks, int[][] prerequisites) {
List<Integer> sortedOrder = new ArrayList<>();
if (tasks <= 0)
return;
// a. Initialize the graph
HashMap<Integer, Integer> inDegree = new HashMap<>(); // count of incoming edges for every vertex
HashMap<Integer, List<Integer>> graph = new HashMap<>(); // adjacency list graph
for (int i = 0; i < tasks; i++) {
inDegree.put(i, 0);
graph.put(i, new ArrayList<Integer>());
}
// b. Build the graph
for (int i = 0; i < prerequisites.length; i++) {
int parent = prerequisites[i][0], child = prerequisites[i][1];
graph.get(parent).add(child); // put the child into it's parent's list
inDegree.put(child, inDegree.get(child) + 1); // increment child's inDegree
}
// c. Find all sources i.e., all vertices with 0 in-degrees
Queue<Integer> sources = new LinkedList<>();
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0)
sources.add(entry.getKey());
}
printAllTopologicalSorts(graph, inDegree, sources, sortedOrder);
}
private static void printAllTopologicalSorts(HashMap<Integer, List<Integer>> graph,
HashMap<Integer, Integer> inDegree, Queue<Integer> sources, List<Integer> sortedOrder) {
if (!sources.isEmpty()) {
for (Integer vertex : sources) {
sortedOrder.add(vertex);
Queue<Integer> sourcesForNextCall = cloneQueue(sources);
// only remove the current source, all other sources should remain in the queue for the next call
sourcesForNextCall.remove(vertex);
List<Integer> children = graph.get(vertex); // get the node's children to decrement their in-degrees
for (int child : children) {
inDegree.put(child, inDegree.get(child) - 1);
if (inDegree.get(child) == 0)
sourcesForNextCall.add(child); // save the new source for the next call
}
// recursive call to print other orderings from the remaining (and new) sources
printAllTopologicalSorts(graph, inDegree, sourcesForNextCall, sortedOrder);
// backtrack, remove the vertex from the sorted order and put all of its children back to consider
// the next source instead of the current vertex
sortedOrder.remove(vertex);
for (int child : children)
inDegree.put(child, inDegree.get(child) + 1);
}
}
// if sortedOrder doesn't contain all tasks, either we've a cyclic dependency between tasks, or
// we have not processed all the tasks in this recursive call
if (sortedOrder.size() == inDegree.size())
System.out.println(sortedOrder);
}
// makes a deep copy of the queue
private static Queue<Integer> cloneQueue(Queue<Integer> queue) {
Queue<Integer> clone = new LinkedList<>();
for (Integer num : queue)
clone.add(num);
return clone;
}
public static void main(String[] args) {
AllTaskSchedulingOrders.printOrders(3, new int[][] { new int[] { 0, 1 }, new int[] { 1, 2 } });
System.out.println();
AllTaskSchedulingOrders.printOrders(4,
new int[][] { new int[] { 3, 2 }, new int[] { 3, 0 }, new int[] { 2, 0 }, new int[] { 2, 1 } });
System.out.println();
AllTaskSchedulingOrders.printOrders(6, new int[][] { new int[] { 2, 5 }, new int[] { 0, 5 }, new int[] { 0, 4 },
new int[] { 1, 4 }, new int[] { 3, 2 }, new int[] { 1, 3 } });
System.out.println();
}
}
269. Alien Dictionary Grokking. Alien Dictionary
There is a dictionary containing words from an alien language for which we don’t know the ordering of the alphabets. Write a method to find the correct order of the alphabets in the alien language. It is given that the input is a valid dictionary and there exists an ordering among its alphabets. Input: Words: ["ywx", "wz", "xww", "xz", "zyy", "zwz"] Output: ywxz Explanation: From the given words we can conclude the following ordering among its characters:
- From "ywx" and "wz", we can conclude that 'y' comes before 'w'.
- From "wz" and "xww", we can conclude that 'w' comes before 'x'.
- From "xww" and "xz", we can conclude that 'w' comes before 'z'
- From "xz" and "zyy", we can conclude that 'x' comes before 'z'
- From "zyy" and "zwz", we can conclude that 'y' comes before 'w' From the above five points, we can conclude that the correct character order is: "ywxz"
这题是一道hard难度的topological sort的题目,主要难点在于,之前我们比较的都是single digit的排序,而现在是让我们通过每个单词在字典里的顺序,来得到character的相对顺序。
突破点在于,既然这是个字典,那里面的word的排序应该都是lexicographically的。我们可以每次通过比较相邻的两个word,来判断每个character在alien language里面的相对顺序。
就拿[“ba”, “bc”, “ac”, “cab”]
这个例子来说:
所以我们知道了,该题是想让我们找到topological order of each character,并且这个order是可以从字典里两两单词的顺序里推理得到的。
所以又回到了我们上面的套路题,只是唯一的不同是,我们build graph的时候,应该要对于两个adjacent words来进行创建先,然后对于我们得到的graph,我们来进行topological sort。
import java.util.*;
class AlienDictionary {
public static String findOrder(String[] words) {
if (words == null || words.length == 0)
return "";
// a. Initialize the graph
HashMap<Character, Integer> inDegree = new HashMap<>(); // count of incoming edges for every vertex
HashMap<Character, List<Character>> graph = new HashMap<>(); // adjacency list graph
for (String word : words) {
for (char character : word.toCharArray()) {
inDegree.put(character, 0);
graph.put(character, new ArrayList<Character>());
}
}
// b. Build the graph
for (int i = 0; i < words.length - 1; i++) {
String w1 = words[i], w2 = words[i + 1]; // find ordering of characters from adjacent words
for (int j = 0; j < Math.min(w1.length(), w2.length()); j++) {
char parent = w1.charAt(j), child = w2.charAt(j);
if (parent != child) { // if the two characters are different
graph.get(parent).add(child); // put the child into it's parent's list
inDegree.put(child, inDegree.get(child) + 1); // increment child's inDegree
break; // only the first different character between the two words will help us find the order
}
}
}
// c. Find all sources i.e., all vertices with 0 in-degrees
Queue<Character> sources = new LinkedList<>();
for (Map.Entry<Character, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0)
sources.add(entry.getKey());
}
// d. For each source, add it to the sortedOrder and subtract one from all of its children's in-degrees
// if a child's in-degree becomes zero, add it to the sources queue
StringBuilder sortedOrder = new StringBuilder();
while (!sources.isEmpty()) {
Character vertex = sources.poll();
sortedOrder.append(vertex);
List<Character> children = graph.get(vertex); // get the node's children to decrement their in-degrees
for (Character child : children) {
inDegree.put(child, inDegree.get(child) - 1);
if (inDegree.get(child) == 0)
sources.add(child);
}
}
// if sortedOrder doesn't contain all characters, there is a cyclic dependency between characters, therefore, we
// will not be able to find the correct ordering of the characters
if (sortedOrder.length() != inDegree.size())
return "";
return sortedOrder.toString();
}
public static void main(String[] args) {
String result = AlienDictionary.findOrder(new String[] { "ba", "bc", "ac", "cab" });
System.out.println("Character order: " + result);
result = AlienDictionary.findOrder(new String[] { "cab", "aaa", "aab" });
System.out.println("Character order: " + result);
result = AlienDictionary.findOrder(new String[] { "ywx", "wz", "xww", "xz", "zyy", "zwz" });
System.out.println("Character order: " + result);
}
}
Grokking. Reconstructing a Sequence 444. Sequence Reconstruction
Given a sequence originalSeq and an array of sequences, write a method to find if originalSeq can be uniquely reconstructed from the array of sequences. Unique reconstruction means that we need to find if originalSeq is the only sequence such that all sequences in the array are subsequences of it.
Grokking/Leetcode上还有一道类似的题目,题意是给定我们一个array of sequences,让我们判断经过topological sort之后,是否能唯一得到题目给的originalSeq。
那其实我们要做的改动就是,在sort的时候,判断当前sources的size是不是大于1的,如果大于1的话,说明当前有多种可能性去sort我们的sequence,则要返回false。并且即使当前的source只有1个,但是当前位置被poll出来的vertex和originalSeq对应位置的数字不一样,说明我们也无法得到一样的originalSeq。
代码如下:
import java.util.*;
class SequenceReconstruction {
public static boolean canConstruct(int[] originalSeq, int[][] sequences) {
List<Integer> sortedOrder = new ArrayList<>();
if (originalSeq.length <= 0)
return false;
// a. Initialize the graph
HashMap<Integer, Integer> inDegree = new HashMap<>(); // count of incoming edges for every vertex
HashMap<Integer, List<Integer>> graph = new HashMap<>(); // adjacency list graph
for (int[] seq : sequences) {
for (int i = 0; i < seq.length; i++) {
inDegree.putIfAbsent(seq[i], 0);
graph.putIfAbsent(seq[i], new ArrayList<Integer>());
}
}
// b. Build the graph
for (int[] seq : sequences) {
for (int i = 1; i < seq.length; i++) {
int parent = seq[i - 1], child = seq[i];
graph.get(parent).add(child);
inDegree.put(child, inDegree.get(child) + 1);
}
}
// if we don't have ordering rules for all the numbers we'll not able to uniquely construct the sequence
if (inDegree.size() != originalSeq.length)
return false;
// c. Find all sources i.e., all vertices with 0 in-degrees
Queue<Integer> sources = new LinkedList<>();
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 0)
sources.add(entry.getKey());
}
// d. For each source, add it to the sortedOrder and subtract one from all of its children's in-degrees
// if a child's in-degree becomes zero, add it to the sources queue
while (!sources.isEmpty()) {
if (sources.size() > 1)
return false; // more than one sources mean, there is more than one way to reconstruct the sequence
if (originalSeq[sortedOrder.size()] != sources.peek())
return false; // the next source (or number) is different from the original sequence
int vertex = sources.poll();
sortedOrder.add(vertex);
List<Integer> children = graph.get(vertex); // get the node's children to decrement their in-degrees
for (int child : children) {
inDegree.put(child, inDegree.get(child) - 1);
if (inDegree.get(child) == 0)
sources.add(child);
}
}
// if sortedOrder's size is not equal to original sequence's size, there is no unique way to construct
return sortedOrder.size() == originalSeq.length;
}
public static void main(String[] args) {
boolean result = SequenceReconstruction.canConstruct(new int[] { 1, 2, 3, 4 },
new int[][] { new int[] { 1, 2 }, new int[] { 2, 3 }, new int[] { 3, 4 } });
System.out.println("Can we uniquely construct the sequence: " + result);
result = SequenceReconstruction.canConstruct(new int[] { 1, 2, 3, 4 },
new int[][] { new int[] { 1, 2 }, new int[] { 2, 3 }, new int[] { 2, 4 } });
System.out.println("Can we uniquely construct the sequence: " + result);
result = SequenceReconstruction.canConstruct(new int[] { 3, 1, 4, 2, 5 },
new int[][] { new int[] { 3, 1, 5 }, new int[] { 1, 4, 2, 5 } });
System.out.println("Can we uniquely construct the sequence: " + result);
}
}
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。 给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。 可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。 请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。 树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
这道题的突破口是,我们要知道,一个树的leave,是不可能成为最小高度树的root的,一个node只有不断往该树的中间靠,才有希望成为最小高度树的root。
所以我们做该题的策略就是,每次找到degree为1的node,即该树的leaves,删去之后继续找subtree的leaves,直到我们最后只剩下1-2个节点,我们就找到了最小高度树的root。
为什么2个节点也可以?看上图的example 2。
所以对此题来说,我们同样可以用topological sort。这个时候,inDegree为1的node就是我们的sources。然后我们开始sort,直到最后剩下的节点只有1-2个,就可以结束我们的while循环。代码如下:
import java.util.*;
class MinimumHeightTrees {
public static List<Integer> findTrees(int nodes, int[][] edges) {
List<Integer> minHeightTrees = new ArrayList<>();
if (nodes <= 0)
return minHeightTrees;
// with only one node, since its in-degree will be 0, therefore, we need to handle it separately
if (nodes == 1) {
minHeightTrees.add(0);
return minHeightTrees;
}
// a. Initialize the graph
HashMap<Integer, Integer> inDegree = new HashMap<>(); // count of incoming edges for every vertex
HashMap<Integer, List<Integer>> graph = new HashMap<>(); // adjacency list graph
for (int i = 0; i < nodes; i++) {
inDegree.put(i, 0);
graph.put(i, new ArrayList<Integer>());
}
// b. Build the graph
for (int i = 0; i < edges.length; i++) {
int n1 = edges[i][0], n2 = edges[i][1];
// since this is an undirected graph, therefore, add a link for both the nodes
graph.get(n1).add(n2);
graph.get(n2).add(n1);
// increment the in-degrees of both the nodes
inDegree.put(n1, inDegree.get(n1) + 1);
inDegree.put(n2, inDegree.get(n2) + 1);
}
// c. Find all leaves i.e., all nodes with only 1 in-degree
Queue<Integer> leaves = new LinkedList<>();
for (Map.Entry<Integer, Integer> entry : inDegree.entrySet()) {
if (entry.getValue() == 1)
leaves.add(entry.getKey());
}
// d. Remove leaves level by level and subtract each leave's children's in-degrees.
// Repeat this until we are left with 1 or 2 nodes, which will be our answer.
// Any node that has already been a leaf cannot be the root of a minimum height tree, because
// its adjacent non-leaf node will always be a better candidate.
int totalNodes = nodes;
while (totalNodes > 2) {
int leavesSize = leaves.size();
totalNodes -= leavesSize;
for (int i = 0; i < leavesSize; i++) {
int vertex = leaves.poll();
List<Integer> children = graph.get(vertex);
for (int child : children) {
inDegree.put(child, inDegree.get(child) - 1);
if (inDegree.get(child) == 1) // if the child has become a leaf
leaves.add(child);
}
}
}
minHeightTrees.addAll(leaves);
return minHeightTrees;
}
public static void main(String[] args) {
List<Integer> result = MinimumHeightTrees.findTrees(5,
new int[][] { new int[] { 0, 1 }, new int[] { 1, 2 }, new int[] { 1, 3 }, new int[] { 2, 4 } });
System.out.println("Roots of MHTs: " + result);
result = MinimumHeightTrees.findTrees(4,
new int[][] { new int[] { 0, 1 }, new int[] { 0, 2 }, new int[] { 2, 3 } });
System.out.println("Roots of MHTs: " + result);
result = MinimumHeightTrees.findTrees(4,
new int[][] { new int[] { 0, 1 }, new int[] { 1, 2 }, new int[] { 1, 3 } });
System.out.println("Roots of MHTs: " + result);
}
}
207. Course Schedule 210. Course Schedule II 269. Alien Dictionary 310. Minimum Height Trees Grokking. All Tasks Scheduling Orders
拓扑排序是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线形排序。
在介绍拓扑排序的模版之前,先来了解一下关于拓扑排序的基本知识:
To find the topological sort of a graph we can traverse the graph in a Breadth First Search (BFS) way. We will start with all the sources, and in a stepwise fashion, save all sources to a sorted list. We will then remove all sources and their edges from the graph. After the removal of the edges, we will have new sources, so we will repeat the above process until all vertices are visited.
这一整个过程可以用如下的过程来visualize: