線性dp:大盜阿福(打家劫舍)

Tomorrowland_D發表於2024-08-24

大盜阿福

  • 本題與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家店鋪的價值

具體細節如下圖所示:

  • img

遍歷順序:

  • 由上面兩步分析可知,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]

    img

最佳化後的邊界處理:

  • 不偷第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]));
    }

總結:

img

  • 我們上面講述的兩種方法,第一種方法叫做分步轉移,第二種方法叫做分類轉移,在有些情況下,二者都能使用,而在某些題目當中,只能使用分類轉移的方法,我們在以後也會介紹的!希望大家能理解這兩種做法。

相關文章