入室搶劫問題 | Medium | LeetCode#198. House Robber
題目敘述

- 題目難度:
Medium - 題目描述: 題目假設你是一個專業的竊賊,街上的屋子都有一筆現金,在街上挨家挨戶的入室竊盜,但如果你偷了相鄰兩間屋子就會觸發自動警報報警,你就會被抓。給定一個整數陣列
nums代表街上每間屋子藏有的錢有多少,請回傳竊賊在不觸發警報的狀況下,可以偷到最大數目的金額。
解法
一開始的想法
我的想法就是,其實就是要找一個陣列 除了相鄰元素外彼此的相加的所有可能組合當中的最大和。舉例來說,給定 nums={2,7,9,3,1},那非相鄰的所有可能組合如下:
1 | 2,9,1 => 12 |
這當中數值最大的和為 12,而因此有了下面的遞迴做法
錯誤解法
1 | class Solution { |
上面的解法的想法在於,對於當前房屋,選擇搶或不搶,如果要搶,就將當前的房子與下下個房子的金額做加總;今天如果不搶,那就移動到下一個房子。 每次都要將當前要搶,跟當前不搶的結果做比對,找出結果金額最大的。 但這樣的做法會有大量重複計算的子問題。因此在 Submit 後出現 Time Limit Exceeded 錯誤
因此接下來,需要進行 我先前DP文章中 所提到的步驟二 Recursion + Memoization
我的解答
1 | class Solution { |
這裡宣告了一個陣列 dp,用來存放每次 robCurrent 以及 skipCurrent 的比較結果,並且將其初始化為 -1
而在 robHelper函數中,可以看到我做的調整,在每一次比較中,如果發現 dp[start] 不等於 -1,就代表對應比較結果前面已經有計算過了,這時候就不需要去重複計算,因此直接返回 dp[start] 即可。
遞迴呼叫圖解
字跡醜陋請包涵




執行結果

最佳化解答
1 | class Solution { |
這裡的更佳解答會是 先前整理的DP文章 中提到的 第三步驟: Iteration + Tabulation,也就是不透過遞迴,而是透過迴圈做到一樣的效果。在程式碼中,首先將 edge case 返回,像是 nums 為空,或者是 nums 只有一個或兩個元素時的狀況。
接著初始化一個長度跟 nums 一樣的陣列 dp 為0,並且該期第一個元素為 nums 包含的第一個元素,而第二個元素則會是
dp陣列初始化

迭代 dp 陣列

執行結果

複雜度
時間複雜度
Recurstion: $O(n)$
在 robHelper 函數中,透過 start 位置從左至右進行遞迴計算,每個位置最多只會計算一次。由於我們使用 dp 來儲存已經計算過的結果,當再次遇到相同的 start 值時,可以直接從 dp 中讀取結果,而不需要重複計算,因此 robHelper 函數在每個 start 位置上僅進行一次計算,從而使整個計算量是線性的,即 $O(n)$
Iteration: $O(n)$
for 遍歷了 nums 陣列,從 i = 2 到 i = nums.size() - 1,因此需要執行 n−2 次迴圈操作。每次迴圈內都是進行簡單計算和比較操作,為 $O(1)$,因此整體時間複雜度為 $O(n)$
空間複雜度
Recursion: $O(n)$
dp,大小為 nums.size() 用於儲存每個 start 位置的計算結果,因此需要 $O(n)$ 大小的空間,並且每個遞迴呼叫 stack 的深度也為 n
Iteration: $O(n)$
dp,大小為 nums.size(),用於儲存每個位置 i 能搶劫的最大金額,因此需要 $O(n)$ 大小的空間










