Shortest Path

1. Floyd

无/有向图,可带负边权。

板子:

public class Floyd {
    public Integer[][] shortestPath(Integer[][] g){
        int n = g.length;
        Integer[][] ans = g.clone(); 
        Integer[][] dp;
        for(int k=0;k<n;k++){
            dp=ans.clone();
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if(i==j){
                        continue;
                    }
                    if(dp[i][j]==null){
                        if(dp[i][j]==null || dp[i][k]==null || dp[k][j]==null){
                            ans[i][j] = null;
                        }
                        else{
                            ans[i][j] = dp[i][k]+dp[k][j];
                        }
                        continue;
                    }
                    ans[i][j] = Math.min(dp[i][j],dp[i][k]+dp[k][j]); // with direction
                }
            }
        }
        return ans;
    }
}

java比较笨,没inf这种标识符。我就用包装类作为边权了,含null。因此特判有点多。但总体而言,状态转移方程都是一样的:

dp(k)(i,j) = min( dp(k-1)(i,k) + dp(k-1)(k,j), dp(k-1)(i,j)

dp(k)(i,j)表示从i到j的最短路,这条路径上除了i,j这两个节点,序号最大为k。

1.1. 周赛377 T3 LC 100156 转换字符串的最小成本Ⅰ

一个字符可以转为另一个字符,代表中间有一条边。很明显就转换为了最短路径和的问题。是一道Floyd的板子题。注意这个题是有向图,x能转换为y不代表y也能转换为x。

另外,小写字母限定了最多只有26个节点,也就是这题是个26^3的O(1)时间复杂度。另外边权比较小只有1e6,这样即便最长链也只有26*1e6,没爆int,dp放心用int,但是几条路径的和可能溢出,因此结果是long。

最后,题目提醒了,这是个多重边的图,因为最短路,很明显把重复的较大权重的边去掉就可以了。

import java.util.Arrays;

class Solution {

    static int invalid = -1;
    public long minimumCost(String source, String target, char[] original, char[] changed, int[] cost) {
        int[][] edge = new int[26][26];
        for (int i = 0; i < edge.length; i++) {
            Arrays.fill(edge[i],invalid);
            edge[i][i]=0;
        }
        int o1,o2;
        for (int i = 0; i < original.length; i++) {
            o1 = original[i]-'a';
            o2 = changed[i]-'a';
            if(o1==o2){
                continue;
            }
            if(edge[o1][o2]!=invalid){
                edge[o1][o2] =  Math.min(edge[o1][o2],cost[i]);
            }else{
                edge[o1][o2] =  cost[i];
            }

        }

        int[][] dp = edge.clone();
        int[][] temp;
        for(int k=0;k<26;k++){
            for(int i=0;i<26;i++){
                for(int j=0;j<26;j++){
                    edge[i][j]  = dp[i][j];
                    if(dp[i][k]!=invalid && dp[k][j]!=invalid){
                        if(edge[i][j]==invalid){
                            edge[i][j]  = dp[i][k]+dp[k][j];
                        }else{
                            edge[i][j]  = Math.min(edge[i][j],dp[i][k]+dp[k][j]);
                        }
                    }
                }
            }
            temp = dp;
            dp = edge;
            edge = temp;
        }

        long ans = 0L;
        for (int i = 0; i < source.length(); i++) {
            int c = dp[source.charAt(i) - 'a'][target.charAt(i) - 'a'];
            if(c == invalid){
                return -1;
            }
            ans += c;
        }
        return ans;
    }
}

这题不带负边权,我就把板子简化了,直接把-1当作不存在边。Floyd是可以把dp和edge矩阵交替用的,省空间。

1.2. LC 1976 到达目的地的方案数

这题我这么想的。首先可以先把0到n-1的最短路求出来。我们知道最短路的子路径也是最短路。所以我们可以建邻接表深搜,查看当前节点是否在一条最短路上:假设当前节点为node,子节点为child,已经走过的距离为dis,最短路矩阵为edges,那么相当于要判断dis+dis(node,child)是否等于edges[0][child]。如果是则可以深搜这个子节点。累积路径数量。

模运算还是那个样子,把能取模的地方全取模。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    int mod = (int)1e9+7;
    List<int[]>[] g;
    int[] memo;
    public int countPaths(int n, int[][] roads) {
        g = new ArrayList[n];
        Arrays.setAll(g,e->new ArrayList<>());

        for (int[] road : roads) {
            g[road[0]].add(new int[]{road[1],road[2]});
            g[road[1]].add(new int[]{road[0],road[2]});
        }

        memo = new int[n];
        Arrays.fill(memo,-1);

        Long[][] edges = new Long[n][n];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(i==j){
                    edges[i][j] = 0L;
                }else{
                    edges[i][j] = null;
                }
            }
        }

        for (int[] road : roads) {
            edges[road[0]][road[1]] = edges[road[1]][road[0]] = (long) road[2];
        }

        Long[][] tmp;

        for (int k = 0; k < n; k++) {
            tmp = edges.clone();
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if(i==j){
                        continue;
                    }
                    if(tmp[i][j]==null){
                        if(tmp[i][k]==null||tmp[k][j]==null){
                            edges[i][j] = null;
                        }else{
                            edges[i][j] = tmp[i][k]+tmp[k][j];
                        }
                    }else{
                        edges[i][j] = tmp[i][j];
                        if(tmp[i][k]!=null && tmp[k][j]!=null){
                            edges[i][j] = Math.min(edges[i][j],tmp[i][k]+tmp[k][j]);
                        }
                    }
                }
            }
        }

        return dfs(0,-1,0L,edges);
    }

    private int dfs(int node,int fa,long dis,Long[][] edges){
        if(node== edges.length-1){
            return 1;
        }

        if(memo[node]!=-1){
            return memo[node];
        }

        int ans = 0;
        for (int[] child : g[node]) {
            if(child[0]!=fa && child[1]+dis==edges[0][child[0]]){
                ans =  (ans % mod + dfs(child[0],node,dis+edges[node][child[0]],edges) % mod) %mod;
            }
        }

        memo[node] = ans;

        return ans;
    }
}

我这做法太sb了。首先Floyd跑了个O(n³),然后记忆化搜索跑了个O(n),总体O(n³),拉爆了。

这题有更好的做法。DIJK同时DP。放到DJIK中吧。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值