1. 程式人生 > 其它 >[LeetCode] 1269. Number of Ways to Stay in the Same Place After Some Steps 停在原地的方案數

[LeetCode] 1269. Number of Ways to Stay in the Same Place After Some Steps 停在原地的方案數


You have a pointer at index0in an array of sizearrLen. At each step, you can move 1 position to the left, 1 position to the right in the array, or stay in the same place (The pointer should not be placed outside the array at any time).

Given two integersstepsand arrLen, return the number of ways such that your pointer still at index0

afterexactlystepssteps. Since the answer may be too large, return itmodulo109+ 7.

Example 1:

Input: steps = 3, arrLen = 2
Output: 4
Explanation: There are 4 differents ways to stay at index 0 after 3 steps.
Right, Left, Stay
Stay, Right, Left
Right, Stay, Left
Stay, Stay, Stay

Example 2:

Input: steps = 2, arrLen = 4
Output: 2
Explanation: There are 2 differents ways to stay at index 0 after 2 steps
Right, Left
Stay, Stay

Example 3:

Input: steps = 4, arrLen = 2
Output: 8

Constraints:

  • 1 <= steps <= 500
  • 1 <= arrLen <= 10^6

這道題說是給了一個長度為 arrLen 的陣列,起始位置在座標0,現在有三種操作,向左移動一個位置,向右移動一個位置,或者是待在原地不動(也算一步),現在需要走 steps 步,問有多少種不同的走法使得最後仍然在座標0的位置,結果需要對一個超大數字取餘。一旦看到了說結果要對一個超大數字取餘,則基本就是暗示了要用動態規劃 Dynmaic Programming 來做,因為遞迴所有情況肯定會爆棧。那麼首先就來定義 dp 陣列吧,既然有步數和座標兩個資訊,大家基本都會定義一個二維陣列,其中 dp[i][j] 表示走了i步,到達座標j位置的不同走法,而且狀態轉移方程也非常的直接,因為當前位置只能由三個位置過來,左邊,右邊,和其本身,而且 i-1 步的 dp 值又都是知道的,所以直接把三個位置的 dp 值加起來就是當前位置的值了,快速寫好後滿心歡喜的去提交,結果被 OJ 打了回來,超時了 Time Limit Exceeded。這道題的 OJ 非常嚴苛,基本上不是超時就是超記憶體,所以要同時進行時間上和空間上的優化,這道題的 trick 主要是在於空間上的優化,需要更新的位置少了,自然時間也就用的少了。

題目中給了 steps 和 arrLen 的範圍,可以發現 arrLen 的範圍要遠大於 steps,但是仔細想一想,起始位置是在0,而最多走 500 步,就算每一步都往右走,最多就只能走 500 個位置,不管之後陣列還有多少位子,都是無法到達的,為無法到達的位置申請空間是沒有意義的,所以能到達的範圍是 steps 和 arrLen 中的較小值,這樣就省下了不少的空間。同時,可以發現第i步的值只跟第 i-1 步的值有關係,所以沒有必要計算每一步的 dp 值,用一個一維的陣列就行了,不過還需要記錄上一步的每一個位置的值,以便更新當前值。分析到這,基本上程式碼就不難寫了,用一個一維陣列 last 來記錄上一步每個位置的 dp 值,i從1遍歷到 steps,新建一個一維陣列 cur 來記錄當前步的值,然後用j從0遍歷到 steps 和 arrLen 中的較小值,若 last[j] 大於0(表示曾經到達過位置j),則將其加到 cur[j] 中並對超大數取餘;若 j+1 小於 arrLen(表示沒有越陣列右邊界),且 last[j+1] 大於0(表示曾經到達過位置 j+1),則將其加到 cur[j] 中並對超大數取餘;若j大於0(表示沒有越陣列左邊界),且 last[j-1] 大於0(表示曾經到達過位置 j-1),則將其加到 cur[j] 中並對超大數取餘。遍歷完所有位置之後,將 cur 賦值給 last,並進行下一步的迴圈,最終的結果儲存在了 last[0] 中,參見程式碼如下:


解法一:

class Solution {
public:
    int numWays(int steps, int arrLen) {
        int M = 1e9 + 7, n = min(steps, arrLen);
        vector<long> last(n + 1);
        last[0] = 1;
        for (int i = 1; i <= steps; ++i) {
            vector<long> cur(n + 1);
            for (int j = 0; j < n; ++j) {
                if (last[j] > 0) {
                    cur[j] = (cur[j] + last[j]) % M;
                }
                if (j + 1 < arrLen && last[j + 1] > 0) {
                    cur[j] = (cur[j] + last[j + 1]) % M;
                }
                if (j > 0 && last[j - 1] > 0) {
                    cur[j] = (cur[j] + last[j - 1]) % M;
                }
            }
            last = cur;
        }
        return last[0];
    }
};

我們也可以使用帶記憶陣列的遞迴解法,可以用一個二維陣列,大小為 steps+1 by steps/2+1,這裡為啥要除以2呢?因為最終的目的是回到位置0,所以最多隻能向右走 steps/2 步。遞迴函式的引數有當前剩餘步數 steps,當前位置i,陣列總長度 arrLen,和記憶陣列 memo,首先判斷若 steps 和 i 均為0,初始化為1,相當於 dp 陣列初始化的種子值。否則繼續判斷,若i小於0,或者i大於等於 arrLen,表示越界了,應該返回0,或者 steps 等於0了,但由於此時不在位置0,也應該返回0,或者i大於 steps,表示剩餘的 steps 步數無法回到位置0,同樣應該返回0。否則看當前的狀態的 memo 值是否大於0,是的話表示該狀態之前計算過了,直接返回 memo[steps][i],否則就要來就計算當前狀態的值,通過對 steps-1 步的右邊位置,左邊位置,和當前位置分別呼叫遞迴函式的值相加,然後對超大數取餘即可,參見程式碼如下:


解法二:

class Solution {
public:
    int numWays(int steps, int arrLen) {
        vector<vector<int>> memo(steps + 1, vector<int>(steps / 2 + 1));
        return dfs(steps, arrLen, 0, memo);
    }
    int dfs(int steps, int arrLen, int i, vector<vector<int>>& memo) {
        if (steps == 0 && i == 0) return 1;
        if (i < 0 || i >= arrLen || steps == 0 || i > steps) return 0;
        if (memo[steps][i] > 0) return memo[steps][i];
        int M = 1e9 + 7;
        return memo[steps][i] = ((dfs(steps - 1, arrLen, i + 1, memo) % M + dfs(steps - 1, arrLen, i - 1, memo)) % M + dfs(steps - 1, arrLen, i, memo)) % M;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1269


參考資料:

https://leetcode.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/

https://leetcode.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/discuss/436117/C%2B%2B-Recursive-DP-(Memoization)

https://leetcode.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/discuss/436287/Step-by-Step-Solution-(diagrams)-(with-how-I-overcome-MemoryTime-limit-exception)


LeetCode All in One 題目講解彙總(持續更新中...)


喜歡請點贊,疼愛請打賞❤️~.~


微信打賞


Venmo 打賞