LeetCode算法練習——動態規劃提高(一)

LeetCode5. 最長迴文子串

給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。

示例 1:

輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。

示例 2:

輸入: "cbbd"
輸出: "bb"

對於一個子串而言,如果它是迴文串,並且長度大於 2,那麼將它首尾的兩個字母去除之後,它仍然是個迴文串。例如對於字符串 “ababa”,如果我們已經知道 “bab”是迴文串,那麼 “ababa” 一定是迴文串,這是因爲它的首尾兩個字母都是 “a”。

根據這樣的思路,我們就可以用動態規劃的方法解決本題。我們用 P(i, j)表示字符串 s 的第 i到 j個字母組成的串是否爲迴文串:

  • P(i, j) = true,如果子串 Si…Sj 是迴文串
  • P(i, j) = false,其他情況

這裏的「其它情況」包含兩種可能性:

  • s[i, j] 本身不是一個迴文串;
  • i > j,此時 s[i, j]本身不合法。

可以寫出動態規劃的狀態轉移方程:P(i, j)=P(i + 1,j − 1) ∧ (Si​ == Sj​)。也就是說,只有 s[i + 1 : j − 1]是迴文串,並且 s 的第 i 和 j個字母相同時,s[i: j]纔會是迴文串。

之前的所有討論是建立在子串長度大於 2 的前提之上的,我們還需要考慮動態規劃中的邊界條件,即子串的長度爲 1 或 2。對於長度爲 1 的子串,它顯然是個迴文串;對於長度爲 2 的子串,只要它的兩個字母相同,它就是一個迴文串。因此我們就可以寫出動態規劃的邊界條件:

  • P(i, i) = true
  • P(i, i + 1) = (Si ​== Si + 1​)​

根據這個思路,就可以完成動態規劃了,最終的答案即爲所有 P(i, j) = true 中 j − i + 1(即子串長度)的最大值。注意:在狀態轉移方程中,我們是從長度較短的字符串向長度較長的字符串進行轉移的,因此一定要注意動態規劃的循環順序。

class Solution {
public:
    //dp[i][j]存放的是[i,,,,j]是否是迴文區間
    //dp[i][j]=dp[i+1][j-1]&&(s[i]==s[j])去除兩端之間的子串是迴文子串,且兩端相等
    string longestPalindrome(string s) {
        int n = s.size();
        string res = "";
        int l = 0;  //l用來記錄當前最長的迴文子串
        if (s.size() == 0) return res;
        if(s.size() == 1)return s;
        res = s[0];//返回子串初始化爲第一個元素
        vector<vector<bool>> dp(n, vector<bool>(n));
          for (int j = 0; j < n; j++) {
            for (int i = j; i >= 0; i--){
                if((s[i] == s[j]) && (j - i <= 2 || dp[i + 1][j - 1])){
                    dp[i][j] = true;
                    if(j - i > l){
                        res = s.substr(i, j - i + 1);
                        l = j - i;
                    }
                }
            }
        }
        return res;
    }
};

LeetCode322. 零錢兌換

給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。

示例 1:

輸入: coins = [1, 2, 5], amount = 11
輸出: 3 
解釋: 11 = 5 + 5 + 1

示例 2:

輸入: coins = [2], amount = 3
輸出: -1

思路:

注意到這個問題有一個最優的子結構性質,這是解決動態規劃問題的關鍵。最優解可以從其子問題的最優解構造出來。如何將問題分解成子問題?假設我們知道 F(S) ,即組成金額 S 最少的硬幣數,最後一枚硬幣的面值是 C。那麼由於問題的最優子結構,轉移方程應爲:F(S) = F(S - C) + 1

但我們不知道最後一枚硬幣的面值是多少,所以我們需要枚舉每個硬幣面額值,即遍歷coins數組並選擇其中的最小值。下列遞推關係成立:

  •  F(S) = min_{_{i = 0,...,n-1}} F(S - C^{_{i}}) + 1 此時 S - C^{_{i}}\geq 0
  • F(S) = 0, S=0
  • F(S) = -1, n=0
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for(int i = 0; i < coins.size(); i++){
            for(int j = coins[i]; j <= amount; j++){
                if(dp[j - coins[i]] != INT_MAX)
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
            }
        }
        if(dp[amount] == INT_MAX)   return -1;
        return dp[amount];
    }
};

LeetCode343. 整數拆分 && 劍指 Offer 14- I. 剪繩子

給你一根長度爲 n 的繩子,請把繩子剪成整數長度的 m 段(m、n都是整數,n>1並且m>1),每段繩子的長度記爲 k[0],k[1]...k[m-1] 。請問 k[0]*k[1]*...*k[m-1] 可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別爲2、3、3的三段,此時得到的最大乘積是18。

示例 1:

輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

建立一維動態數組 dp:

  •     邊界條件:dp[1] = dp[2] = 1,表示長度爲 2 的繩子最大乘積爲 1;
  •     狀態轉移方程:dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j])),以下爲LeetCode一題解給出的示意圖:

class Solution {
public:
    int cuttingRope(int n) {
        vector<int> dp(n + 1,0);
        dp[1] = 1, dp[2] = 1;
        for(int i = 3; i <= n; i++){
            for(int j = 1; j < i; j++)
                dp[i] = max(dp[i], max((i - j) * j, j * dp[i - j])); 
        }
        return dp[n];
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章