【Leetcode】847. Shortest Path Visiting All Nodes

题目地址:

https://leetcode.com/problems/shortest-path-visiting-all-nodes/

给定一个 n n n阶无向连通图,顶点编号 1 ∼ n − 1 1\sim n-1 1n1,求最短的哈密顿环路长度。

BFS解法参考https://blog.csdn.net/qq_46105170/article/details/109913464。下面介绍动态规划方法。

这里可以用状态压缩,令一个数 x x x的二进制位分别表示某个顶点是否被访问过,再令 f [ i ] [ x ] f[i][x] f[i][x]表示当前位于编号 i i i的顶点,且面对的局面是 x x x的时候(意思是已访问和未访问的点的状态是 x x x),走完全部的点的最短路的长度。则 f [ i ] [ 2 n − 1 ] = 0 f[i][2^n-1]=0 f[i][2n1]=0,代表所有点都已经走完了,那显然最短路长度就是 0 0 0。对于别的情况,有: f [ i ] [ s ] = 1 + min ⁡ i → k { f [ k ] [ s ∣ ( 1 < < k ) ] } f[i][s]=1+\min_{i\to k}\{f[k][s|(1<<k)]\} f[i][s]=1+ikmin{f[k][s(1<<k)]}最后的解就是 min ⁡ i = 0 , 1 , . . . , n − 1 f [ i ] [ 1 < < i ] \min_{i=0,1,...,n-1}f[i][1<<i] mini=0,1,...,n1f[i][1<<i]。显然循环的时候 s s s要从 2 n − 2 → 0 2^{n}-2\to 0 2n20这样的顺序来循环,但这里有一个问题,如果在求 f [ i ] [ s ] f[i][s] f[i][s]的时候,恰好要用到某个 f [ j ] [ s ] f[j][s] f[j][s](这里的关键是有可能遇到 s ∣ ( 1 < < k ) = s s|(1<<k)=s s(1<<k)=s的情况),但此时 f [ j ] [ s ] f[j][s] f[j][s]可能还没算出来,这样会导致错误答案。解决方法是,只要发现某个 f [ i ] [ s ] f[i][s] f[i][s]被更新为更小值了,就再进行一次循环,直到循环得不到更优解为止。这里说明一下为什么不会有死循环,如果有死循环的话,就会得到”自己更新自己“的情形,但这个图的边权都是 1 1 1,并不存在负环,所以不会死循环。代码如下:

import java.util.Arrays;

public class Solution {
    public int shortestPathLength(int[][] graph) {
        int n = graph.length;
        int[][] dp = new int[n][1 << n];
        for (int[] row : dp) {
            Arrays.fill(row, n * n);
            row[(1 << n) - 1] = 0;
        }
        
        for (int s = (1 << n) - 2; s > 0; s--) {
            for (boolean repeat = true; repeat; ) {
                repeat = false;
                for (int cur = 0; cur < n; cur++) {
                    if ((s >> cur & 1) == 1) {
                        for (int next : graph[cur]) {
                            int ne = s | (1 << next);
                            if (1 + dp[next][ne] < dp[cur][s]) {
                                dp[cur][s] = 1 + dp[next][ne];
                                // 一旦发现被更新为更优解了,则追加一次循环
                                repeat = true;
                            }
                        }
                    }
                }
            }
        }
        
        int res = n * n;
        for (int i = 0; i < n; i++) {
            res = Math.min(res, dp[i][1 << i]);
        }
        
        return res;
    }
}

时间复杂度 O ( V 2 2 V ) O(V^22^V) O(V22V),空间 O ( V 2 V ) O(V2^V) O(V2V)

状态设置不唯一。我们也可以设 f [ i ] [ s ] f[i][s] f[i][s]为从 i i i出发,形成状态 s s s的最短路长度。此时初始条件就是 f [ i ] [ 1 < < i ] = 0 f[i][1<<i]=0 f[i][1<<i]=0,要返回的答案则是 min ⁡ i = 0 , 1 , . . . . n − 1 f [ i ] [ 2 n − 1 ] \min_{i=0,1,....n-1}f[i][2^n-1] mini=0,1,....n1f[i][2n1],转移方程为: f [ k ] [ s ∣ ( 1 < < k ) ] = 1 + min ⁡ i → k { f [ i ] [ s ] } f[k][s|(1<<k)]=1+\min_{i\to k}\{f[i][s]\} f[k][s(1<<k)]=1+ikmin{f[i][s]}代码如下:

import java.util.Arrays;

public class Solution {
    public int shortestPathLength(int[][] graph) {
        int n = graph.length;
        int[][] dp = new int[n][1 << n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(dp[i], n * n);
            dp[i][1 << i] = dp[i][0] = 0;
        }
        
        for (int s = 1; s < 1 << n; s++) {
            for (boolean repeat = true; repeat; ) {
                repeat = false;
                for (int cur = 0; cur < n; cur++) {
                    if ((s >> cur & 1) == 1) {
                        for (int next : graph[cur]) {
                            if (1 + dp[cur][s] < dp[next][s | (1 << next)]) {
                                dp[next][s | (1 << next)] = 1 + dp[cur][s];
                                repeat = true;
                            }
                        }
                    }
                }
            }
        }
        
        int res = n * n;
        for (int i = 0; i < n; i++) {
            res = Math.min(res, dp[i][(1 << n) - 1]);
        }
        
        return res;
    }
}

时空复杂度一样。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值