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

LeetCode劍指 Offer 46. 把數字翻譯成字符串

給定一個數字,我們按照如下規則把它翻譯爲字符串:0 翻譯成 “a” ,1 翻譯成 “b”,……,11 翻譯成 “l”,……,25 翻譯成 “z”。一個數字可能有多個翻譯。請編程實現一個函數,用來計算一個數字有多少種不同的翻譯方法。

示例 1:

輸入: 12258
輸出: 5
解釋: 12258有5種不同的翻譯,分別是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

此題最關鍵的是將數值轉換成字符串,然後判定連續兩個字符串的範圍是不是在“00”~“25”之間。

基本情況下:dp[i] += dp[i - 1];因爲每個字符都可以表示一個字母,這是基本情況。

接下來我們對連續兩個字符的進行判斷:

  • 如果高位爲“1”也不爲“2”則跳過情況,因爲此時不會出現其他翻譯方法;
  • 如果高位爲“2”且低位大於“5”則跳過情況,因爲此時不會出現其他翻譯方法;
  • 當連續兩個字符串的範圍在“00”~“25”之間,dp[i] += dp[i - 2]。
class Solution {
public:
    int translateNum(int num) {
        string value = to_string(num);
        vector<int> dp(value.size() + 1, 0);
        dp[0] = 1; dp[1] = 1;
        for(int i = 2; i < dp.size(); i++){
            dp[i] += dp[i - 1];
            if(value[i - 2] != '1' && value[i - 2] != '2')  
                continue;               //高位不是1,2,此時不會出現其他翻譯方法
            if(value[i - 2] == '2' && value[i - 1] > '5')   
                continue;               //連續的兩位數值大於25,此時不會出現其他翻譯方法
            dp[i] += dp[i - 2];
        }
        return dp[value.size()];
    }
};

LeetCode劍指 Offer 60. n個骰子的點數

把n個骰子扔在地上,所有骰子朝上一面的點數之和爲s。輸入n,打印出s的所有可能的值出現的概率。你需要用一個浮點數數組返回答案,其中第 i 個元素代表這 n 個骰子所能擲出的點數集合中第 i 小的那個的概率。

示例 1:

輸入: 1
輸出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:

輸入: 2
輸出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

表示狀態

分析問題的狀態時,不要分析整體,只分析最後一個階段即可!因爲動態規劃問題都是劃分爲多個階段的,各個階段的狀態表示都是一樣,而我們的最終答案在就是在最後一個階段。對於這道題,最後一個階段是什麼呢?通過題目我們知道一共投擲 n 枚骰子,那最後一個階段很顯然就是:當投擲完 n 枚骰子後,各個點數出現的次數。

注意,這裏的點數指的是前 n 枚骰子的點數和,而不是第 n 枚骰子的點數。

找出了最後一個階段,那狀態表示就簡單了。

  •     首先用數組的第一維來表示階段,也就是投擲完了幾枚骰子。
  •     然後用第二維來表示投擲完這些骰子後,可能出現的點數。
  •     數組的值就表示,該階段各個點數出現的次數。

所以狀態表示就是這樣的:dp[i][j],表示投擲完 i 枚骰子後,點數 j 的出現次數。

找出狀態轉移方程

找狀態轉移方程也就是找各個階段之間的轉化關係,同樣我們還是隻需分析最後一個階段,分析它的狀態是如何得到的。最後一個階段也就是投擲完 n 枚骰子後的這個階段,我們用 dp[n][j] 來表示最後一個階段點數 j 出現的次數。

單單看第 n 枚骰子,它的點數可能爲 1,2,3,...,6,因此投擲完 n 枚骰子後點數 j 出現的次數,可以由投擲完 n−1 枚骰子後,對應點數 j−1,j−2,j−3,...,j−6出現的次數之和轉化過來。寫成數學公式是這樣的:

dp[n][j] = \sum_{i = 1}^{6}dp[n-1][j-i]

n 表示階段,j 表示投擲完 n 枚骰子後的點數和,i 表示第 n 枚骰子會出現的六個點數。

邊界處理

這裏的邊界處理很簡單,只要我們把可以直接知道的狀態初始化就好了。我們可以直接知道的狀態是啥,就是第一階段的狀態:投擲完 1 枚骰子後,它的可能點數分別爲 1,2,3,...,6,並且每個點數出現的次數都是 1。

class Solution {
public:
    vector<double> twoSum(int n) {
        //點數和共有6*n種情況,可能性數組長度爲6*n - n + 1 = 5*n + 1
        vector<vector<double>> dp(n + 1, vector<double>(6*n + 1, 0));
        vector<double> res;
        if(n == 0)  res.push_back(0);
        for(int i = 1; i <= n; i++){
            for(int j = i; j <= 6*i; j++){
                if(i == 1){     //投擲一次時,出現的次數均爲1,即初始化
                    dp[i][j] = 1;
                    continue;
                }
                for(int k = 1; k <= 6; k++){
                    //投擲完 i 枚骰子後點數 j 出現的次數,可以由投擲完 i−1 枚骰子後,
                    //對應點數 j−1, j−2, j−3,..., j−6出現的次數之和轉化過來,其和不小於i-1
                    if(j - k >= i - 1)    dp[i][j] = dp[i][j] + dp[i - 1][j - k];
                }
            }
        }
        for(int i = n; i <= 6*n; i++)
            res.push_back(dp[n][i] / pow(6, n));
        return res;
    }
};

LeetCode32. 最長有效括號

給定一個只包含 '(' 和 ')' 的字符串,找出最長的包含有效括號的子串的長度。

示例 1:

輸入: "(()"
輸出: 2
解釋: 最長有效括號子串爲 "()"

示例 2:

輸入: ")()())"
輸出: 4
解釋: 最長有效括號子串爲 "()()"

此題如果用暴力法做,利用棧來實現括號匹配問題最後幾個用例會出現超時,可以改用動態規劃解決該類問題。我們定義一個 dp 數組,其中第 i 個元素表示以下標爲 i 的字符結尾的最長有效子字符串的長度。這裏需要考慮兩種情況,非嵌套匹配“()()”和嵌套匹配“(())”

非嵌套匹配“()()”:當s[i] = ')'時,如果s[i - 1] = '('則匹配,此時dp[i] = dp[i  -  2]  +  2;

嵌套匹配“(())”:我們一般針對這種格式的第二個 ')',即滿足條件(i - dp[i - 1] - 1 >= 0) && s[i - dp[i - 1] - 1] == '(' ,這裏的i - dp[i - 1] - 1 >= 0 確保能夠進行該格式的匹配判斷:

  • 如果嵌套括號前有括號“()(())”,我們則需要計算上嵌套括號匹配前的括號匹配數量,即加上dp[i - dp[i - 1] - 2],此時狀態轉移方程爲:dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2;
  • 沒有的話:dp[i] = dp[i - 1] + 2。
class Solution {
public:
    //暴力法超時
    /*bool isValid(string str){
        stack<char> s;
        for(int i = 0; i < str.size(); i++){
            if(str[i] == '(')   s.push(str[i]);
            else if(!s.empty() && s.top() == '(')   s.pop();
            else    return false;
        }
        return s.empty();
    }

    int longestValidParentheses(string s) {
        int maxlen = 0;
        for(int i = 0; i < s.size(); i++){
            for(int j = 2; j + i <= s.size(); j += 2){
                if(isValid(s.substr(i, j))){
                    if(maxlen < j)  maxlen = j;
                }
            }
        }
        return maxlen;
    }*/
    int longestValidParentheses(string s) {
        int maxlen = 0;
        vector<int> dp(s.size(), 0);            //到第 i 個格子爲止,匹配的子串長度
        for(int i = 1; i < s.size(); i++){
            if(s[i] == ')'){
                if(s[i - 1] == '('){
                    if(i - 2 >= 0)  dp[i] = dp[i - 2] + 2;
                    else dp[i] = 2;
                }
                //嵌套括號的情況:(())
                else if((i - dp[i - 1] - 1 >= 0) && s[i - dp[i - 1] - 1] == '('){
                    //嵌套括號前是否有括號:()(())
                    if(i - dp[i - 1] - 2 >= 0)  dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2;
                    else    dp[i] = dp[i - 1] + 2;
                }
            }
            maxlen = max(maxlen, dp[i]);
        }
        return maxlen;
    }
};

 

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