[LeetCode] 1269. Number of Ways to Stay in the Same Place After Some Steps 停在原地的方案數
You have a pointer at index0
in 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 integerssteps
and arrLen
, return the number of ways such that your pointer still at index0
steps
steps. 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/
LeetCode All in One 題目講解彙總(持續更新中...)
微信打賞 |
Venmo 打賞 |