前言
力扣经常看到评论区名言:简单题我重拳出击,中等题我努力思考,困难题我复制粘贴。
以前碰到力扣图论问题的时候好多都是直接放弃挣扎,被认定为困难题我铺好床垫,化身cv工程师。之前在春招和秋招的时候因为时间问题,最后一道图论也大多会放弃,专攻前面两道。
最近有更多一点的空闲时间,就打算把它捡起来,下定决心迎难而上,做几道高频专题先练练手,想着说不定能够总结出一些套路或者经典模板操作出来,避免“谈图色变”,下次碰到图相关的题目倒不至于直接投了。下面是近一周的经典高频题目打卡总结。
一、力扣743. 网络延迟时间-单源最短路问题
据说是少有的单源最短路直接套模板的题目。常见的模板解法包括Dijkstra和Floyd算法(目前还在接受能力范围之内)。
1.Dijkstra解法
class Solution {
public int networkDelayTime(int[][] times, int n, int k) {
final int INF = Integer.MAX_VALUE / 2;
int[][] graph = new int[n][n];
boolean[] visited = new boolean[n];
int[] dist = new int[n];
Arrays.fill(dist, INF);
for (int i = 0; i < n; i++) {
Arrays.fill(graph[i], INF);
}
for (int[] time : times) {
graph[time[0] - 1][time[1] - 1] = time[2];
}
// 更新k到其余各点的距离
for (int i = 0; i < n; i++) {
dist[i] = graph[k - 1][i];
}
dist[k - 1] = 0;
visited[k - 1] = true;
// k节点已遍历,只需遍历剩余n-1个节点
for (int i = 0; i < n - 1; i++) {
int min = INF;
int u = -1;
// 找到未访问节点中距离最近的节点
for (int j = 0; j < n; j++) {
if (!visited[j] && dist[j] < min) {
min = dist[j];
u = j;
}
}
// u=-1说明剩余的节点都不可达,直接返回-1
if (u == -1) {
return -1;
}
visited[u] = true;
// 以u节点为准更新u到其余节点的距离
for (int j = 0; j < n; j++) {
// 如果k经过u到达j的距离小于k直接到j的距离,则更新dist,取最小的距离
if (!visited[j] && graph[u][j] + dist[u] < dist[j]) {
dist[j] = graph[u][j] + dist[u];
}
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, dist[i]);
}
return ans == INF ? -1 : ans;
}
}
其实还可以利用优先级队列进行优化。除了枚举,我们还可以使用一个小根堆来寻找「未确定节点」中与起点距离最近的点。
class Solution {
public int networkDelayTime(int[][] times, int n, int k) {
final int INF = Integer.MAX_VALUE / 2;
List<int[]>[] g = new List[n];
for (int i = 0; i < n; ++i) {
g[i] = new ArrayList<int[]>();
}
for (int[] t : times) {
int x = t[0] - 1, y = t[1] - 1;
g[x].add(new int[]{y, t[2]});
}
int[] dist = new int[n];
Arrays.fill(dist, INF);
dist[k - 1] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
pq.offer(new int[]{0, k - 1});
while (!pq.isEmpty()) {
int[] p = pq.poll();
int time = p[0], x = p[1];
if (dist[x] < time) {
continue;
}
for (int[] e : g[x]) {
int y = e[0], d = dist[x] + e[1];
if (d < dist[y]) {
dist[y] = d;
pq.offer(new int[]{d, y});
}
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans == INF ? -1 : ans;
}
}
2.Floyd解法
class Solution {
public int networkDelayTime(int[][] times, int n, int k) {
final int INF = Integer.MAX_VALUE / 2;
int[][] dist = new int[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(dist[i], INF);
}
for (int[] time : times) {
dist[time[0] - 1][time[1] - 1] = time[2];
}
for (int v = 0; v < n; v++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] > dist[i][v] + dist[v][j]) {
dist[i][j] = dist[i][v] + dist[v][j];
}
}
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
if (i != k - 1) {
ans = Math.max(ans, dist[k - 1][i]);
}
}
return ans == INF ? -1 : ans;
}
}
二、力扣133. 克隆图-DFS/BFS
这个其实不难,用DFS或者BFS遍历拷贝就行。
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
*/
class Solution {
public Node cloneGraph(Node node) {
if(node==null) return node;
HashMap<Node,Node> visited = new HashMap<>();
Queue<Node> que = new LinkedList<>();
que.offer(node);
visited.put(node,new Node(node.val,new ArrayList<>()));
while(!que.isEmpty()){
Node n = que.poll();
for(Node neighbor:n.neighbors){
if(!visited.containsKey(neighbor)){
Node cloneNode = new Node(neighbor.val,new ArrayList<>());
visited.put(neighbor,cloneNode);
que.offer(neighbor);
}
visited.get(n).neighbors.add(visited.get(neighbor));
}
}
return visited.get(node);
}
}
三、力扣210. 课程表 II-拓扑排序
本题是一道经典的「拓扑排序」问题。尽管官方给出了DFS和BFS的解法,但是都不如拓扑排序理解的更直接。
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] dig = new int[numCourses];
for(int[] prerequisite:prerequisites){
dig[prerequisite[0]]++;
}
Queue<Integer> que = new LinkedList<>();
for(int i=0;i<dig.length;i++){
if(dig[i]==0) que.offer(i);
}
int[] res = new int[numCourses];
int count = 0;
while(!que.isEmpty()){
int code = que.poll();
res[count++] = code;
for(int[] prerequisite:prerequisites){
if(prerequisite[1]==code){
dig[prerequisite[0]]--;
if(dig[prerequisite[0]]==0){
que.offer(prerequisite[0]);
}
}
}
}
if(count==numCourses) return res;
return new int[0];
}
}
四、力扣310. 最小高度树-拓扑排序(度的概念)
还是拓扑排序,加入度的概念更好理解。
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
//最终输出结果集
List<Integer> res = new ArrayList<Integer>();
if(n==1){
res.add(0);
return res;
}
//定义度 & 建图
int[] degree = new int[n];
//边的集合
List<Integer>[] adj = new List[n];
for(int i=0;i<n;i++){
adj[i] = new ArrayList<Integer>();
}
for(int[] edge:edges){
adj[edge[0]].add(edge[1]);
adj[edge[1]].add(edge[0]);
degree[edge[0]]++;
degree[edge[1]]++;
}
//所有入度为1的节点 入队列
Queue<Integer> que = new ArrayDeque<Integer>();
for(int i=0;i<n;i++){
if(degree[i]==1){
que.offer(i);
}
}
//出队 直到节点数剩余<=2
int remain = n;
while(remain>2){
int size = que.size();
remain-=size;
for(int i=0;i<size;i++){
int id = que.poll();
for(int node: adj[id]){
degree[node]--;
if(degree[node]==1){
que.offer(node);
}
}
}
}
//合并结果
while(!que.isEmpty()){
res.add(que.poll());
}
return res;
}
}
五、力扣329. 矩阵中的最长递增路径-记搜可以解(困难题)
不一定要用拓扑排序,记忆化搜索可以解决,将矩阵看成一个有向图,每个单元格对应图中的一个节点,如果相邻的两个单元格的值不相等,则在相邻的两个单元格之间存在一条从较小值指向较大值的有向边。问题转化成在有向图中寻找最长路径。
class Solution {
public int longestIncreasingPath(int[][] matrix) {
int n = matrix.length, m = matrix[0].length;
if(matrix==null || n==0 || m==0) return 0;
int res = 0;
int[][] memo = new int[n][m];
int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};
for(int i=0;i<n;i++){
for(int j= 0;j<m;j++){
res = Math.max(res,dfs(matrix,i,j,memo,dirs));
}
}
return res;
}
public int dfs(int[][] matrix,int i,int j, int[][] memo,int[][] dirs){
if(memo[i][j]!=0) return memo[i][j];
memo[i][j]++;
for(int[] dir:dirs){
int newX = i+dir[0];
int newY = j+dir[1];
if(newX>=0&&newX<matrix.length&&newY>=0&&newY<matrix[0].length&&matrix[newX][newY]>matrix[i][j])
memo[i][j] = Math.max(memo[i][j],dfs(matrix,newX,newY,memo,dirs)+1);
}
return memo[i][j];
}
}
六、迷宫问题,走出迷宫的最少步数
DFS或者BFS都行,BFS空间占用大,但是速度快。
DFS是碰到头再返回重新开始,耗时长,空间占用小。
https://blog.csdn.net/weixin_46175201/article/details/133929515
总结
本文总结了最近一周自己刷的图论相关的题目,包括有向/无向无环图的dfs和bfs 、拓扑排序、单源最短路Dijkstra和Floyd算法等,之前总是想放弃的,现在我考虑正面面对它。