状态转移方程的定义为:dp( K,i ) 表示经历 K 站乘坐到 flight[i] 航班终点的最低票价。
因为 flight 中的元素存在前后置关系,所以乘坐某航班的上一航班的集合是可以确定的。
dp( K,i ) = Math.min( dp( K-1,j ) ),其中 j 为可以作为上一趟航班的航班下标集合。
/** * @Author Niuxy * @Date 2020/10/18 4:53 下午 * @Description 按出发地选中开头航班后,不断通过比较目的地与下一出发地寻找可能的下一航班 * 因为每趟航班的目的地是确定的,不管从哪条路线来到该航班,最终结果只受剩余可乘坐航班次数的限制 * 因此可以通过航班坐标 flag 及剩余可乘坐航班次数 K 来进行结果的缓存 * 那么可以以这两个维度进行 DP 递推 */ public final int findCheapestPriceDP(int n, int[][] flights, int src, int dst, int K) { int len = flights.length; int[][] cache = new int[K + 1][len]; int re = Integer.MAX_VALUE; for (int i = 0; i < cache[0].length; i++) { if (flights[i][0] != src) continue; if (flights[i][1] == dst) re = Math.min(re, flights[i][2]); else cache[K][i] = flights[i][2]; } for (int i = K - 1; i >= 0; i--) { for (int j = 0; j < len; j++) { int minPreCost = Integer.MAX_VALUE; for (int k = 0; k < len; k++) { if (cache[i + 1][k] == 0 || flights[j][0] != flights[k][1]) continue; else minPreCost = Math.min(minPreCost, cache[i + 1][k]); } if (minPreCost != Integer.MAX_VALUE) if (flights[j][1] == dst) re = Math.min(re, minPreCost + flights[j][2]); else cache[i][j] = minPreCost + flights[j][2]; } } return re == Integer.MAX_VALUE ? -1 : re; }
在第三层 for 循环中,我们在以 O(N) 的时间复杂度寻找上一趟航班可能的下标。整个状态转移方程没有用到参数 n 。
如果以 n 为维度建立缓存表,遍历 flight 时可以直接以 O(1) 的时间复杂度找到上一趟航班;另外,DP 的过程只涉及到 K 与 K-1 行,可以借此优化空间复杂度:
public final int findCheapestPriceDP2(int n, int[][] flights, int src, int dst, int K) { int flag = Integer.MAX_VALUE / 2; int[][] dp = new int[2][n]; dp[0][src] = dp[1][src] = 0; Arrays.fill(dp[0], flag); Arrays.fill(dp[1], flag); for (int k = 0; k <= K; k++) { for (int[] info : flights) { dp[k & 1][info[1]] = Math.min(dp[k & 1][info[1]], dp[~k & 1][info[0]] + info[2]); } } return dp[K & 1][dst] < Integer.MAX_VALUE / 2 ? dp[K & 1][dst] : -1; }
JS 写法:
/** * @param {number} n * @param {number[][]} flights * @param {number} src * @param {number} dst * @param {number} K * @return {number} */ var findCheapestPrice = function (n, flights, src, dst, K) { let dp = []; for (let i = 0; i < K + 2; i++) { dp.push(new Array(n).fill(Number.MAX_VALUE)); dp[i][src] = 0; } let an = Number.MAX_VALUE; for (let i = 1; i < K + 2; i++) { for (let j = 0; j < flights.length; j++) { dp[i][flights[j][1]] = Math.min(dp[i][flights[j][1]], dp[i - 1][flights[j][0]] + flights[j][2]); } } return dp[K + 1][dst] == Number.MAX_VALUE ? -1 : dp[K + 1][dst]; };