大盜阿福
- 本題與leetcode198題——打家劫舍的題意一模一樣,閱讀完本文以後可以嘗試以下題目
力扣題目連結)
題目敘述:
阿福是一名經驗豐富的大盜。趁著月黑風高,阿福打算今晚洗劫一條街上的店鋪。這條街上一共有N家店鋪,每家店中都有一些現金。阿福事先調查得知,只有當他同時洗劫了兩家相鄰的店鋪時,街上的報警系統才會啟動,然後警察就會蜂擁而至。作為一向謹慎作案的大盜阿福不願意冒著被警察追捕的風險行竊。他想知道,在不驚動警察的情況下,他今晚最多可以得到多少現金?
輸入格式
- 輸入的第一行是一個整數T,表示一共有T組資料。
- 接下來的每組資料,第一行是一個整數N,表示一有N家店鋪。
- 第二行是N個被空格分開的正整數,表示每一家店鋪中的現金數量。每家店鋪中的現金數量均不超過1000。
輸出格式
- 對於每組資料,輸出一行。該行包含一個整數,表示阿福在不驚動警察的情況下可以得到的現金數量。
輸入樣例:
2
3
1 8 2
4
10 7 6 14
輸出樣例:
8
24
樣例解釋:
- 對於第一組樣例,阿福選擇第2家店鋪行竊,獲得的現金數量為8。對於第二組樣例,阿福選擇第1和4家店鋪行竊獲得的現金數量為10+14=24.
動態規劃思路分析
- 設我們打劫的店鋪數量為
i
,獲取的價值和為dp
,那麼dp明顯是i
的一個函式,那麼我們就用dp[i]
作為狀態變數,dp[i]
表示偷前i
家店鋪所能獲取的價值最大值
狀態變數以及它的含義
- 由上面分析可知,我們設立
dp[i]
作為狀態變數,並且dp[i]
的含義是偷前i
家店鋪所能獲取收益的最大值.
遞推公式
- 我們設
dp[i]
,在i的這個位置有兩種狀態:- 1.第i家店鋪不偷——
dp[i]=dp[i-1]
- 2.第i家店鋪偷——
dp[i]=dp[i-2]+w[i]
,w[i]為第i家店鋪的價值
- 1.第i家店鋪不偷——
具體細節如下圖所示:
遍歷順序:
- 由上面兩步分析可知,
dp[i]
的狀態一定是由前面dp[i-1]
,dp[i-2]
,推出來的,所以說遍歷順序一定是從前向後遍歷。
如何初始化?
- 我們首先得處理好邊界條件:
dp[0]
和dp[1]
怎麼處理? - 偷前0家店鋪的最大價值顯然是
0
,偷前1家店鋪的最大價值顯然為w[1]
- 處理好邊界條件以後,我們再從前向後,依據遞推公式進行遞推就行了
舉例驗證dp陣列
下標:1,2,3,4
w[i]:10,7,6,14
dp[i]:10,10,16,24
- 透過樣例2分析可知,我們的dp陣列沒有分析錯。因此我們驗證了我們的dp陣列的正確性。
最佳化
-
我們可以用
dp[i-1]
的狀態直接推出dp[i]
的狀態。 -
我們狀態表示可以最佳化成:
f[i][0]
表示不偷第i家店鋪能獲取的最大值f[i][1]
表示偷第i家店鋪能獲取的最大值
-
那麼我們的狀態轉移方程就可以從
dp[i-1]
推出,不偷第i家店鋪,那麼我們就可以偷第i-1家店鋪,也可以不偷,我們選取這兩個之中的最大值,如果偷第i家店鋪的話,第i-1家店鋪我們一定只能選擇不偷。- 不偷:
dp[i][0]=max(dp[i-1][0],dp[i-1][1])
- 偷:
dp[i][1]=dp[i-1][0]+w[i]
- 不偷:
最佳化後的邊界處理:
- 不偷第1家店鋪:
f[i][0]=0
- 偷第1家店鋪:
f[i][1]=w[1]
最佳化後的程式碼處理:
scanf("%d",&t)
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
f[1][0]=0;f[1][1]=w[1];
for(int i=2;i<=n;i++){
f[i][0]=max(f[i-1][0],f[i-1][1]);
f[i][1]=f[i-1][0]+w[i];
}
printf("%d\n",max(f[n][0],f[n][1]));
}
總結:
- 我們上面講述的兩種方法,第一種方法叫做分步轉移,第二種方法叫做分類轉移,在有些情況下,二者都能使用,而在某些題目當中,只能使用分類轉移的方法,我們在以後也會介紹的!希望大家能理解這兩種做法。