演算法之DP——秋葉收藏集

it_was發表於2020-10-02

難度中等:confused:

小扣出去秋遊,途中收集了一些紅葉和黃葉,他利用這些葉子初步整理了一份秋葉收藏集 leaves, 字串 leaves 僅包含小寫字元 ry, 其中字元 r 表示一片紅葉,字元 y 表示一片黃葉。
出於美觀整齊的考慮,小扣想要將收藏集中樹葉的排列調整成「紅、黃、紅」三部分。每部分樹葉數量可以不相等,但均需大於等於 1。每次調整操作,小扣可以將一片紅葉替換成黃葉或者將一片黃葉替換成紅葉。請問小扣最少需要多少次調整操作才能將秋葉收藏集調整完畢。

示例 1:
輸入:leaves = “rrryyyrryyyrr”
輸出:2
解釋:調整兩次,將中間的兩片紅葉替換成黃葉,得到 “rrryyyyyyyyrr”

示例 2:
輸入:leaves = “ryr”
輸出:0
解釋:已符合要求,不需要額外操作

提示:
3 <= leaves.length <= 10^5
leaves 中只包含字元 ‘r’ 和字元 ‘y’

一開始的思路就是單純的透過幾次迭代找出左邊出現 r 和 y 的相關資訊和右邊出現 r 和 y 的相關資訊,然後對所有情況進行討論,結果發現,情況好多,需要判斷的很多:sob:

經過一番鬥爭,,,,,發現最合適的還是用動態規劃,感覺只要題中出現求最少或者最多的都可以用DP,因為動態規劃的初衷就是透過一些已經計算好的資訊去尋找一個最優解。
既然想著可以用DP,那麼就需要尋找狀態,因為我們需要將字串整成【紅,黃,紅】的排列,所以可以定義三個狀態:用0和2表示前面的紅色和後面的紅色,用1表示中間的黃色

  • 之後我們可以定義一個二維陣列 int[leaves.length()][3]dp[i][j]表示 leaves[0…..i] 的字串狀態為 j 的最小操作次數:bangbang:
  • 首先是狀態 0 ,即前面的部分是[紅],那當前 dp[i][0] 就取決於前面一個字元在狀態0時的最小操作次數加上本位上是否是紅色!
  • 然後是狀態 1 ,即前面的部分是[紅,黃], 那當前 dp[i][1] 就是要取前面一個字元處於狀態 0 和狀態 1 的最小值 ( 因為前面是【紅】還是 【紅,黃】都可以,畢竟本位上一定會調整為黃,而使0….i一定為狀態 1 !)加上本位是否是黃色!
  • 最終是狀態 2,即前面的部分是[紅,黃,紅]!, 那當前 dp[i][2] 就是取前面一個字元處於狀態 1 和狀態 2 的最小值(*因為狀態 1 是【紅,黃】,狀態 2 是 【紅,黃,紅】都滿足條件的,畢竟本位最終會調整為 紅色,使 0…i 一定滿足【紅,黃,紅】,而狀態 0 是不行的,因為 【紅】的情況再怎麼調整也無法調整為 【紅,黃,紅】! *) 加上本位是否是紅色!

注意:boom::boom::boom:

由於 因為每一種狀態包含的葉子數量必須至少為 1,因此不僅需要特別注意 j=2 時的狀態轉移方程,還需要考慮每個狀態本身是否是符合要求的。對於狀態 dp[i][j]而言,它包含了leaves[ 0..i ] 共 i+1 片葉子以及 j+1 個狀態,因此 葉子的數量必須大於等於狀態的數量,即滿足 i≥j。 這樣一來,所有 i < j 的狀態 dp[i][j] 都是不符合要求的。 觀察上面的狀態轉移方程,我們在每一步轉移時都是取最小值,因此我們可以將所有不符合要求的狀態置為一個極大值(例如 n+1 或整數型別的上限等)。

這種利用以前的狀態資訊來加速本位上的狀態收集,並最終求出最優解的方法就是dp,所以至關重要的是如何找出正確的狀態,以及狀態方程!:clap: :clap: :clap:

下面展示一下程式碼:

class Solution {
    public int minimumOperations(String leaves) {
        int len = leaves.length();
        int[][] dp = new int[len][3];
        dp[0][0] = leaves.charAt(0) == 'y' ? 1 : 0;
        dp[0][1] = dp[0][2] = dp[1][2] = Integer.MAX_VALUE;//需要注意這點,即葉子的數量一定是大於等於狀態的總數的!
        for (int i = 1; i < n; ++i) {
            int isRed = leaves.charAt(i) == 'r' ? 1 : 0;
            int isYellow = leaves.charAt(i) == 'y' ? 1 : 0;
            //以下這三步需要好好體會!
            dp[i][0] = dp[i - 1][0] + isYellow;
            dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][1]) + isRed;
            if (i >= 2) {
               dp[i][2] = Math.min(dp[i - 1][1], dp[i - 1][2]) + isYellow;
            }
        }
        return dp[len - 1][2];
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章