1. 程式人生 > 其它 >動態規劃(十八)可行路徑數

動態規劃(十八)可行路徑數

問題描述

本問題對應 LeetCode 1575. 統計所有可行路徑](https://leetcode-cn.com/problems/count-all-possible-routes/)。

具體描述如下:給定一個互不相同的整數陣列 locations,其中 locations[i] 表示第 i 個 城市的位置,同時給定三個引數 startfinishfuel 分別表示出發城市、目的城市和初始的汽油量。在每一步中,可以任意選擇一個一個城市 j,從上一座城市 i 移動到城市 j 需要消耗的汽油量為 |locations[j] - locations[i]||x| 表示 x 的絕對值。現在需要編寫一個函式,求得從 start

finish 之間滿足條件的可能行駛方案的總數。

前提條件:

  1. 可以經過一個城市多次,包括 startfinish,但是不能每次必須在城市之間進行移動
  2. 必須確保在城市之間移動的汽油量 fuel 不能是負的
  3. 由於答案可能會很大,因此需要對最終的結果對 1e9 + 7 進行取餘操作

解決思路

對於路徑查詢的問題,首先會想到使用 dfs 的方式遍歷所有的可能路徑,找到符合條件的路徑進行統計即可。

  • 一般DFS

    按照一般的 DFS 的方法進行處理即可,但是需要注意以下幾點:

    • 由於可以在起始位置和結束位置來回,因此目的城市的位置不是 DFS 的結束條件
    • 由於需要保證有充足的汽油量在城市之間進行走動,因此汽油量就是 DFS
      的終止條件
    • 每當有一條路徑能夠到達目的城市時,說明至少存在這麼一條路徑
  • 帶記憶化的 DFS

    一般的 DFS 解法在這個問題中會超時,這是因為在 DFS 的過程中大量重複計算了之前的已經計算過的情況,由於重複計算的原因導致上文提到的一般的 DFS 解決思路的時間複雜度是指數級別的。

    對於這種由於重複計算導致高額的時間複雜度的情況,一般的解決方案都是使用動態規劃的方式記錄之前的計算結果,這樣可以可以有效降低演算法的時間複雜度。

    定義二維陣列 dp[pos][rest] 表示當前的城市位置為 pos、可用汽油量為 rest 的條件下,可以到達目的城市的路徑總數。記

    \[cost_{pos,i} = |locations[pos] - locations[i]| \]

    表示從 pos

    i 需要消耗的汽油量,那麼 dp[pos][i] 的狀態轉換函式如下所示:

    \[dp[pos][rest] = \sum_{i=0}^{n-1} dp[i][rest -cost_{pos,i}] (其中 rest >= cost_{pos,i}) \]

    邊界情況:噹噹前所處的城市的位置為 finish 時,此時至少存在一種可能的路徑,因此對 dp[finish][rest] 需要額外加一

    進一步的優化:(原題並沒有描述這一特徵,但是在官方的題解中介紹到了)在兩個城市之間穿梭,在最短的距離中的耗油量是最小的,因此在兩個城市之間可能的路徑數在這種情況下的數量是最多的(多餘的 fuel 可以進行更多的路徑移動),因此如果在搜尋過程中發現有耗油量大於這個值的,那麼就可以直接忽略掉,即 dp[pos][rest] = 0

實現

  • 一般 DFS

    class Solution {
        private final static int mod = (int)(1e9 + 7);
        private int target; // 目標城市位置
    
        public int countRoutes(int[] locations, int start, int finish, int fuel) {
            target = finish;
    
            return dfs(locations, start, fuel);
        }
    
        /**
        * @param location : 城市的位置資訊列表
        * @param cur : 當前所處的城市位置
        * @param curFuel:當前行駛過程可用的汽油量
        */
        private int dfs(int[] locations, int cur, int curFuel) {
            int sum = 0, take = 0;
            if (cur == target) sum = 1; // 這裡是邊界情況,處於目的城市時至少存在一條可能的路徑
    
            for (int i = 0; i < locations.length; ++i) {
                if (i == cur) continue;
    
                take = Math.abs(locations[cur] - locations[i]); // 從當前城市 cur 到城市 i 需要花費的汽油量
                if (take > curFuel) continue; // 要保證能夠從一個城市到另一個城市
                
                sum += dfs(locations, i, curFuel - take); // 遞迴搜尋即可
                sum %= mod;
            }
    
            return sum;
        }
    }
    

    複雜度分析,由於對每個位置都需要進行 DFS 搜尋,因此時間複雜度為 $ O(n^fuel) $ (其中,n 表示城市列表的長度,fuel 表示初始的可用汽油量)

  • 帶記憶化的 DFS

    class Solution {
        private static final int mod = (int)(1e9 + 7);
        int[][] dp;
        int n;
    
        public int countRoutes(int[] locations, int start, int finish, int fuel) {
            n = locations.length;
            dp = new int[n][fuel + 1];
    
            for (int i = 0; i < n; ++i)
                Arrays.fill(dp[i], -1); // 將它填充為 -1,這是為了區分可能路徑數為 0 和已經訪問過這兩種情況
    
            return dfs(locations, start, finish, fuel);
        }
    
        private int dfs(int[] locations, int pos, int finish, int rest) {
            if (dp[pos][rest] != -1) // 不為 -1 表示已經被訪問過了,直接拿取結果即可
                return dp[pos][rest];
    
            dp[pos][rest] = 0; // 標記為已經訪問過這個情況
            if (Math.abs(locations[pos] - locations[finish]) > rest) // 耗油量最小的情況是可能路徑數最大的
                return 0;
            
            for (int i = 0; i < n; ++i) {
                if (pos == i) continue; // 必須移動到別的城市
    
                int cost = Math.abs(locations[pos] - locations[i]);
                if (cost > rest) continue;
                dp[pos][rest] += dfs(locations, i, finish, rest - cost);
                dp[pos][rest] %= mod;
            }
    
            // 注意這裡的邊界情況
            if (pos == finish) {
                dp[pos][rest] += 1;
                dp[pos][rest] %= mod;
            }
    
            return dp[pos][rest];
        }
    }
    

    複雜度分析:相比較一般的 DFS 方式的搜尋,通過動態規劃的方式來記錄之前的訪問情況,大幅度降低了計算的時間複雜度,最終時間複雜度為 \(O(fuel*n^2)\)