在图论中,**拓扑排序(Topological Sorting)**是一个有向无环图的所有顶点的线性序列,且该序列满足下面两个条件
1.每个顶点只出现一次
2.若在序列中顶点A出现在顶点B的前面 ,那么在途中不存在B到A的路径
TIPS:除了可以对有向无环图进行排序外,还可以看是不是排序的结果只有一种可能即是否存在分支,可以通过每次循环判断队列的长度来实现
思路: 有向无环图求最长路径可以考虑使用拓扑排序
1.我们初始化m,n,outdegrees,ans
2.我们遍历,matrix,并向四周进行扩展,若比当前元素大,就有一条边相连,outdegrees[i][j]++,初始化队列再次遍历outdegrees并将出度为0的元素加入队列中
3.遍历队列,若队列不为空,ans++,弹出队首元素,并向四周扩展,若该元素的四周有元素比当前元素小就度数-1,若度数为0就加入队列中
4.返回ans
int[] fx = {0, 0, -1, 1}, fy = {-1, 1, 0, 0};
public int longestIncreasingPath(int[][] matrix) {
//step1
int m = matrix.length, n = matrix[0].length, ans = 0;
int[][] outdegrees = new int[m][n];
//step2
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < 4; k++) {
int newX = i + fx[k], newY = j + fy[k];
if(inArea(newX, newY, m, n) && matrix[i][j] < matrix[newX][newY]){
outdegrees[i][j]++;
}
}
}
}
Queue<int[]> queue = new ArrayDeque<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(outdegrees[i][j] == 0) queue.offer(new int[]{i, j});
}
}
//step3
while(!queue.isEmpty()){
ans++;
int size = queue.size();
for (int i = 0; i < size; i++) {
int[] cur = queue.poll();
int x = cur[0], y = cur[1];
for (int k = 0; k < 4; k++) {
int newX = x + fx[k], newY = y + fy[k];
if(inArea(newX, newY, m, n) && matrix[x][y] > matrix[newX][newY]){
outdegrees[newX][newY]--;
if(outdegrees[newX][newY] == 0){
queue.offer(new int[] {newX, newY});
}
}
}
}
}
return ans;
}
boolean inArea(int x, int y, int m, int n){
return x >= 0 && x < m && y >= 0 && y < n;
}
思路: 若a在b的前面则不存在,b -> a的路径,该问题可以用拓扑排序解决
1.我们初始化edges,indegrees和ans
2.我们遍历prerequisites将,对应的边加入edges中,并累加该点的入度,初始化队列,将入度为0的点加入队列中
3.当队列不为空,我们就弹出该点,并将该点加入ans中,遍历该点的边,并将指定点的,入度-1,判断是否有入度为0的点,若有就加入队列中
4.若index != numCourses则说明不可能完成所有的课程,返回空数组,否则返回ans
public int[] findOrder(int numCourses, int[][] prerequisites) {
//step1
List<List<Integer>> edges = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
edges.add(new ArrayList<>());
}
int[] indegrees = new int[numCourses];
int[] ans = new int[numCourses];
//step2
for(int[] prerequisite : prerequisites){
int x = prerequisite[0], y = prerequisite[1];
edges.get(y).add(x);
indegrees[x]++;
}
int idx = 0;
Queue<Integer> queue = new ArrayDeque<>();
for (int i = 0; i < numCourses; i++) {
if(indegrees[i] == 0) queue.offer(i);
}
//step3
while(!queue.isEmpty()){
int cur = queue.poll();
ans[idx++] = cur;
for(int edge : edges.get(cur)){
indegrees[edge]--;
if(indegrees[edge] == 0){
queue.offer(edge);
}
}
}
//step4
if(idx != numCourses) return new int[0];
return ans;
}
思路: 本题要求我们对有向无环的图进行排序,可以用拓扑排序来解决,这是一道经典的拓扑排序题
步骤:
1.我们首先对map、indeg、n进行初始化操作,其中map是将题目中提供的words转化为图,indeg代表点的入度,用于判断排序规则是否合法
2.然后我们先对图和indeg进行初始化操作再根据题目提供的words进行构图操作并判断排序规则是否合法,若不合法直接返回空字符串
3.对map进行去重,初始化队列,初始化StringBuilder,并将入度为0的点加入队列中
4.若队列不为空,就弹出当前头结点,并将该字符加入进StringBuilder中,遍历与该点想连的点,并将相邻的点入度-1,若相邻点的入度为0就加入队列中
5.先判断StringBuilder的长度是否等于n,若相等则返回重构后的字符串,否则返回空字符串
class Solution {
//step1
Map<Character, List<Character>> map = new HashMap<>();
Map<Character, Integer> indeg = new HashMap<>();
int n = 0;
public String alienOrder(String[] words) {
int size = words.length;
//step2
for (int i = 0; i < words.length; i++) {
int sL = words[i].length();
char[] chars = words[i].toCharArray();
for (int j = 0; j < sL; j++) {
if(!map.containsKey(chars[j])){
n++;
map.put(chars[j], new ArrayList<>());
}
if(!indeg.containsKey(chars[j])){
indeg.put(chars[j], 0);
}
}
}
for (int i = 1; i < size; i++) {
if(!addEdge(words[i - 1], words[i])) return "";
}
Queue<Character> queue = new ArrayDeque<>();
for(Map.Entry<Character, Integer> entry : indeg.entrySet()){
if(entry.getValue() == 0) queue.offer(entry.getKey());
}
//step4
StringBuilder stringBuilder = new StringBuilder(n);
while(!queue.isEmpty()){
char cha = queue.poll();
stringBuilder.append(cha);
for(char c : map.get(cha)){
indeg.put(c, indeg.get(c) - 1);
if(indeg.get(c) == 0) queue.offer(c);
}
}
return stringBuilder.length() == n ? stringBuilder.toString() : "";
}
/**
* 添加边的方法,若判断边非法,则返回false
* @param s1
* @param s2
* @return
*/
boolean addEdge(String s1, String s2){
int n1 = s1.length(), n2 = s2.length();
int len = Math.min(n1, n2);
int idx = 0;
while(idx < len){
char c1 = s1.charAt(idx), c2 = s2.charAt(idx);
if(c1 != c2){
map.get(c1).add(c2);
indeg.put(c2, indeg.get(c2) + 1);
break;
}
idx++;
}
if(idx == n2 && n1 > n2) return false;
return true;
}
}
思路: 根据条件给定一个有向无环图的顺序,直接用拓扑排序,这里因为唯一的情况才能返回true,因此每执行一次循环我们就看queue中是不是只有一个元素
若不是则说明存在分支,因此直接返回false
步骤:
1.初始化map和indeg
2.根据条件将sequence中的有向边加入图中,并对点的入度进行累加
3.初始化队列和idx,遍历nums,看indeg中是否有该点,若没有则说明该点入度为0,直接加入队列中
4.若队列不为空,就获取当前队列的大小,若大小大于1则直接返回false,否则弹出当前元素,idx+1,并看当前该点的边,相连接的点入度-1,判断相连接的点入度是否为0,若为0加入队列中
5.若idx != nums的长度,则返回false,否则返回true
class Solution {
public boolean sequenceReconstruction(int[] nums, int[][] sequences) {
//step1
Map<Integer, List<Integer>> map = new HashMap<>();
Map<Integer, Integer> indeg = new HashMap<>();
for(int i : nums){
map.putIfAbsent(i, new ArrayList<>());
}
//step2
for(int[] arr : sequences){
for (int i = 1; i < arr.length; i++) {
int prev = arr[i - 1], cur = arr[i];
map.get(prev).add(cur);
indeg.put(cur, indeg.getOrDefault(cur, 0) + 1);
}
}
//step3
int idx = 0;
Queue<Integer> queue = new ArrayDeque<>();
for(int i : nums){
if(!indeg.containsKey(i)){
queue.offer(i);
}
}
//step4
while(!queue.isEmpty()){
//判断是否存在多种可能,即存在分支
if(queue.size() > 1) return false;
idx++;
int i = queue.poll();
for(int edge : map.get(i)){
indeg.put(edge, indeg.get(edge) - 1);
if(indeg.get(edge) == 0){
queue.offer(edge);
}
}
}
//step5
//判断是否有环
return idx == nums.length ? true : false;
}
}