leetcode-45-jump-game-ii
題目
You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0].
Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any nums[i + j] where:
- 0 <= j <= nums[i] and
- i + j < n
Return the minimum number of jumps to reach nums[n - 1]. The test cases are generated such that you can reach nums[n - 1].
Example 1
1 | Input: nums = [2,3,1,1,4] |
Example 2
1 | Input: nums = [2,3,0,1,4] |
解釋題目
給你一個 array,叫做 nums,可以把 nums 想像成一個樓梯,每一層樓都是用 index 來表示,起始位置會是第 0 層,而樓梯上的數字代表可以前進的最大步數。跟 前一題 很像,但這題的前提是: 一定可以走到頂樓,只是要回傳最少步伐,最後要回傳步數。
思路 (1) 動態規劃
用動態規劃的方式解題,前一題 是如果能走到 i 樓層,我就去計算在第 i 樓能走到的最遠距離。所以把
dp [i]
設定為可以達到 i 樓層的的布林值。但是,此題的dp[i] 要設定成能跳到第 i 樓的最少步數。一開始我只知道
dp[0]
是 0,因為不用走,其他層dp[i]
不知道會要跳幾步,所以先給無限大float('inf')
dp 在這題的想法是,每當我跳到第 i 層時,我從第 i 層跳到第 j 層,只要 1 步。
以
[2, 3, 1, 1, 4]
為例。當我跳到第 0 層時,我可以從第 0 層可以跳到第 1 或第 2 層,而且只要 1 步,因此步數會是dp[0] + 1
。所以,我今天要比較的是:- 跳到 1 樓的最小步數比較少 ( 也就是
dp[1]
的設定) ,還是從我當前樓層dp[i]
多跳 1 步 比較少 ( 也就是dp[0] + 1
) - 跳到 2 樓的最小步數比較少 ( 也就是
dp[2]
的設定) ,還是從我當前樓層dp[i]
多跳 1 步 比較少 ( 也就是dp[0] + 1
)
別忘記我們的定義 dp[i] 是能 跳到第 i 樓的最少步數
- 跳到 1 樓的最小步數比較少 ( 也就是
想通了這個概念,應該就大致理解 dp 在這題的應用。還沒想通可以繼續思考: 當我跳到第 1 層時,我可以從第 1 層可以跳到第 2、 3 、4 層,而且只要 1 步,因此步數會是
dp[1] + 1
。所以,我今天要比較的是:- 跳到 2 樓的最小步數比較少 ( 也就是
dp[2]
的設定) ,還是從我當前樓層dp[i]
多跳 1 步 比較少 ( 也就是dp[1] + 1
) - 跳到 3 樓的最小步數比較少 ( 也就是
dp[3]
的設定) ,還是從我當前樓層dp[i]
多跳 1 步 比較少 ( 也就是dp[1] + 1
) - 跳到 4 樓的最小步數比較少 ( 也就是
dp[4]
的設定) ,還是從我當前樓層dp[i]
多跳 1 步 比較少 ( 也就是dp[1] + 1
)
- 跳到 2 樓的最小步數比較少 ( 也就是
表格化如下,一開始的 dp 值都是設定無限大。
當前樓層 i
跳的範圍 比較跳到 j 樓最少步數 dp[j]
&
當前樓層多跳一步dp[i] + 1
比較的結果 0 1 dp[1] = min(dp[1], dp[0] + 1)
min(inf, 0+1) = 1
2 dp[2] = min(dp[2], dp[0] + 1)
min(inf, 0+1) = 1
1 2 dp[2] = min(dp[2], dp[1] + 1)
min(1, 1+1) = 1
3 dp[3] = min(dp[3], dp[1] + 1)
min(inf, 1+1) = 2
4 dp[4] = min(dp[4], dp[1] + 1)
min(inf, 1+1) = 2
2 3 dp[3] = min(dp[3], dp[2] + 1)
min(2, 1+1) = 2
3 4 dp[4] = min(dp[4], dp[3] + 1)
min(2, 2+1) = 2
注意: 當樓層數 < 2 時,也就是只有 1 樓時,就不需要跳躍了。
此解法的時間複雜度 O(n^2),空間複雜度 O(n)。
程式碼 (1) 動態規劃
1 | class Solution: |
思路 (2) Greedy
用貪婪法解題,我感覺比較抽象。貪婪法主要的想法是
- 紀錄每走一層樓時,能到達到的最遠距離,此步驟可以確認每一步都有計算到
- 甚麼時候要跳,當我達到前一次跳躍的位置時,我就必須要跳,跳的距離是,我經過的樓層中所記綠到的最遠距離
以
[2, 3, 1, 1, 4]
為例,我從第一樓開始跳,一開始都還沒跳,所以前一次跳躍的位置 ( last_jump_index ) 是 0。一開始就要跳了,但此時只記錄到第 0 層樓可以跳的最遠距離,也就是
i + nums[i]
,所以 i = 0 時,最遠距離 ( farest ) 是 2。- 此時,我要跳到最遠距離 2,所以 last_jump_index 是 2。
接著,記錄到第 1 樓時,可跳躍的最遠距離是
1 + 3 = 4
,比 2 還大,所以最遠距離 ( farest ) 變成 4。接著,記錄到第 2 樓時,可跳躍最遠距離是
2 + 1 = 3
,比 4 還小,所以最遠距離 ( farest ) 還是 4。- 此時,我不得不跳了,因為已經達到前一跳的位置 2 ,所以我要從 index 2 跳到最遠距離 4 ,所以 last_jump_index 是 4。
之後以此類推即可,我們還可以在迴圈加個條件,就是當
last_jump_index >= n
時,就終止迴圈,因為已經抵達了。
雖然這樣想比較抽象,因為我的跳躍是抵達前一次跳躍的 last_jump_index 時,記錄到的最遠距離, 0 -> 2 -> 4,但其實我們真正跳躍的樓層是 0 -> 1 -> 4,因為我只要記錄我會跳幾次,從哪層樓跳並不重要。
程式碼 (2) Greedy
1 | class Solution: |
思路 (3) BFS
用 BFS 解題,BFS 是 Breadth First Search 廣度優先的縮寫,主要是一層一層去搜尋,再慢慢往下一層搜尋。這題可用 BFS,主要是因為我們要去計算每一層可以到達的所有節點。
以
[2, 3, 1, 1, 4, 1, 2, 1, 1, 2]
為例,從第「零」階 2 出發,要嘛走到第「一」階、要嘛走到第「二」階,所以 第一階 ~ 第二階 是 第一層 的範圍。也就是說:從起始點走到第一層,只需要 1 步。而第一、二階是起始點可以抵達的所有節點。
此時,我們要遍歷第一層的所有數字,找出第二層的範圍。第「一」階 3 最遠可以走到第「四」階;第「二」階 1 最遠可以走到第「三」階。因為第二階已經在第一層的範圍內了,所以 第二層 的範圍是 第三階 ~ 第四階。也就是說:
從第一層走到第二層,也只需要一步。而第三、四階是從第一層可以抵達的所有節點。
此時,似乎有一些端倪。我們就是要 尋找每一層的起始點跟終點。而每一層的範圍可以想像是一個 window 的概念,每一層就是走一步。而 終止的條件就是: 在某一層的終點碰到最後一階。
所以每一層的起始點 ( left ) 是左指針,終點 ( right ) 是右指針。終止條件是當
right < len(nums) - 1
時,因為當 right 碰到頂樓,就代表不用再走了。一開始,左右指針都在第「零」階的位置。所以第 0 層的範圍會是
range( left, right + 1 )
,要包含右指針 ( right ),所以 right 要 + 1,因為要去計算第 0 階可以走到的所有節點範圍。在每一層的範圍裡,要去計算最遠距離 ( farest ),會是每個 range 裡的
i + nums[i]
,而下一層起始範圍就會是上一層終點的下一個位置,也就是left = right + 1
,而下一層起始範圍就會是最遠距離 ( farest )。每經過一層,跳躍次數 ( jump ) 就要 + 1 次。
圖像拆解
程式碼 (3) BFS
1 | class Solution: |