動態規劃DP問題分類和經典題型

解題關鍵:

解結構特徵,抽象出狀態,寫成狀態轉移方程。

動態規劃理念:

1.最優化原理
   1951年美國數學家R.Bellman等人,根據一類多階段問題的特點,把多階段決策問題變換爲一系列互相聯繫的單階段問題,然後逐個加以解決。一些靜態模型,只要人爲地引進“時間”因素,分成時段,就可以轉化成多階段的動態模型,用動態規劃方法去處理。與此同時,他提出瞭解決這類問題的“最優化原理”(Principle of optimality):
    “一個過程的最優決策具有這樣的性質:即無論其初始狀態和初始決策如何,其今後諸策略對以第一個決策所形成的狀態作爲初始狀態的過程而言,必須構成最優策略”。簡言之,一個最優策略的子策略,對於它的初態和終態而言也必是最優的。
    這個“最優化原理”如果用數學化一點的語言來描述的話,就是:假設爲了解決某一優化問題,需要依次作出n個決策D1,D2,…,Dn,如若這個決策序列是最優的,對於任何一個整數k,1 < k < n,不論前面k個決策是怎樣的,以後的最優決策只取決於由前面決策所確定的當前狀態,即以後的決策Dk+1,Dk+2,…,Dn也是最優的。
    最優化原理是動態規劃的基礎。任何一個問題,如果失去了這個最優化原理的支持,就不可能用動態規劃方法計算。能採用動態規劃求解的問題都需要滿足一定的條件: 
    (1) 問題中的狀態必須滿足最優化原理
    (2) 問題中的狀態必須滿足無後效性
    所謂的無後效性是指:“下一時刻的狀態只與當前狀態有關,而和當前狀態之前的狀態無關,當前的狀態是對以往決策的總結”。

2.問題求解模式 
    動態規劃所處理的問題是一個多階段決策問題,一般由初始狀態開始,通過對中間階段決策的選擇,達到結束狀態。這些決策形成了一個決策序列,同時確定了完成整個過程的一條活動路線(通常是求最優的活動路線)。如圖所示。動態規劃的設計都有着一定的模式,一般要經歷以下幾個步驟。

   初始狀態→│決策1│→│決策2│→…→│決策n│→結束狀態
     圖1 動態規劃決策過程示意圖

    (1)劃分階段:按照問題的時間或空間特徵,把問題分爲若干個階段。在劃分階段時,注意劃分後的階段一定要是有序的或者是可排序的,否則問題就無法求解。
    (2)確定狀態和狀態變量:將問題發展到各個階段時所處於的各種客觀情況用不同的狀態表示出來。當然,狀態的選擇要滿足無後效性。
    (3)確定決策並寫出狀態轉移方程:因爲決策和狀態轉移有着天然的聯繫,狀態轉移就是根據上一階段的狀態和決策來導出本階段的狀態。所以如果確定了決策,狀態轉移方程也就可寫出。但事實上常常是反過來做,根據相鄰兩段各狀態之間的關係來確定決策。
    (4)尋找邊界條件:給出的狀態轉移方程是一個遞推式,需要一個遞推的終止條件或邊界條件。

3.實現
    動態規劃的主要難點在於理論上的設計,也就是上面4個步驟的確定,一旦設計完成,實現部分就會非常簡單。使用動態規劃求解問題,最重要的就是確定動態規劃三要素:問題的階段,每個階段的狀態以及從前一個階段轉化到後一個階段之間的遞推關係。遞推關係必須是從次小的問題開始到較大的問題之間的轉化,從這個角度來說,動態規劃往往可以用遞歸程序來實現,不過因爲遞推可以充分利用前面保存的子問題的解來減少重複計算,所以對於大規模問題來說,有遞歸不可比擬的優勢,這也是動態規劃算法的核心之處。確定了動態規劃的這三要素,整個求解過程就可以用一個最優決策表來描述,最優決策表是一個二維表,其中行表示決策的階段,列表示問題狀態,表格需要填寫的數據一般對應此問題的在某個階段某個狀態下的最優值(如最短路徑,最長公共子序列,最大價值等),填表的過程就是根據遞推關係,從1行1列開始,以行或者列優先的順序,依次填寫表格,最後根據整個表格的數據通過簡單的取捨或者運算求得問題的最優解。下面分別以求解最大化投資回報問題和最長公共子序列問題爲例闡述用動態規劃算法求解問題的一般思路。


動態規劃經典例題


1.三角形找一條從頂到底的最小路徑

分析

設狀態爲 f (i; j ),表示從從位置 (i; j ) 出發,路徑的最小和,則狀態轉移方程爲

f(i,j)=min{f(i+1,j),f(i+1,j+1)}+(i,j)

 

2.最大子數組和

設狀態爲 f[j],表示以 S[j] 結尾的最大連續子序列和,狀態轉移方程如下:

f=max(f+A[i],A[i]);//對於數組裏的一個整數,它只有兩種 選擇:1、加入之前的 SubArray2. 自己另起一個 SubArray

maxsum=max(maxsum,f);// 求字串中最大的

 

3.迴文最小劃分次數

對輸入的字符串劃分爲一組迴文字符串,最小的分割次數

所以要轉換成一維 DP。如果每次,從 i 往右掃描,每找到一個迴文就算一次 DP 的話,就可以

轉換爲 f(i)= 區間 [i, n-1] 之間最小的 cut 數,爲字符串長度,則狀態轉移方程爲

 

 

4.最佳時間買賣股票

設狀態f(i)表示區間[0,i-1]上的最大利潤,設置狀態g(i),表示區間[i,n-1]上最大利潤。

則最大利潤爲max{f(i)+g(i)};允許在一天內買進又賣出,相當於不交易,因爲題目的規定是最多兩次,而不是一定要兩次


5. 判斷字符串s3是否由s1,s2交叉存取組成

設狀態 f[i][j],表示 s1[0,i]  s2[0,j],匹配 s3[0, i+j]。如果 s1 的最後一個字符等  s3 的最後一個字符,則

 f[i][j]=f[i-1][j]

如果 s2 的最後一個字符等於 s3 的最後一個字符,  

f[i][j]=f[i][j-1]

因此狀態轉移方程如下: 

f[i][j] = (s1[i - 1] == s3 [i + j - 1] && f[i - 1][j]) || (s2[j - 1] == s3 [i + j - 1] && f[i][j - 1]);

 

6.給定一個矩形表格,求從頂到底的最小和

Minimum Path Sum

設狀態爲 f[i][j],表示從起點 (0; 0) 到達 (i; j ) 的最小路徑和,則狀態轉移方程爲:

f[i][j]=min(f[i-1][j], f[i][j-1])+grid[i][j]

 

7.使兩個字符串相等,最小的編輯次數

Edit Distance

設狀態爲 f[i][j],表示 A[0,i]  B[0,j] 之間的最小編輯距離。設 A[0,i] 的形式是

str1cB[0,j] 的形式是 str2d

1. 如果 c==d,則 f[i][j]=f[i-1][j-1]

2. 如果 c!=d

(a) 如果將 c 替換成 d,則 f[i][j]=f[i-1][j-1]+1

(b) 如果在 c 後面添加一個 d,則 f[i][j]=f[i][j-1]+1

(c) 如果將 c 刪除,則 f[i][j]=f[i-1][j]+1


8.給定一串數字,1對應A2對應B,26對應Z,求有多少種解碼方式

 Decode Ways

和爬樓梯問題一樣,

 f (n) 表示爬 n 階樓梯的不同方法數,爲了爬到第 n 階樓梯,有兩個選擇:

• 從第 n-1 階前進 1 步;

• 從第 n-2 階前進 2 步;

因此,有 f (n) = f (n-1) + f (n-2) 這是一個斐波那契數列。

 

9. 不同的子序列Distinct Subsequences

給定2個字符串a, b,求ba中出現的次數。要求可以是不連續的,但是ba中的順序必須和b以前的一致。 

Here is an example: S = "rabbbit", T = "rabbit"

Return 3.

 

類似於數字分解的題目。dp[i][j]表示:b的前j個字符在a的前i個字符中出現的次數

 

如果S[i]==T[j],那麼dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

意思是:如果當前S[i]==T[j],那麼當前這個字母即可以保留也可以拋棄,所以變換方法等於保留這個字母的變換方法加上不用這個字母的變換方法。
如果S[i]!=T[i],那麼dp[i][j] = dp[i-1][j]

意思是如果當前字符不等,那麼就只能拋棄當前這個字符

遞歸公式中用到的dp[0][0] = 1dp[i][0] = 0(把任意一個字符串變換爲一個空串只有一個方法

 

10.單詞分解Word Break

字符串是否可以分解爲給定的單詞

For example, given

s = "leetcode",

dict = ["leet", "code"].

dp[i]  表示源串的前i個字符可以滿足分割,那麼 dp[ j ] 滿足分割的條件是存在使得 dp [k] && substr[k,j]在字典裏。




真題:



1.Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.


分析
狀態爲 f (i; j ),表示從從位置 (i; j ) 出發,路徑的最小和,則狀態轉移方程爲
f(i,j)=min{f(i+1,j),f(i+1,j+1)}+(i,j)

代碼
// LeetCode, Triangle
// 時間複雜度 O(n^2),空間複雜度 O(1)
class Solution {
public:
int minimumTotal (vector<vector<int>>& triangle) 
{
    for (int i = triangle.size() - 2; i >= 0; --i)
    {
        for (int j = 0; j < i + 1; ++j)
            triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1]); 
    }
    return triangle [0][0]; 
};

2.Maximum Subarray

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [−2,1,−3,4,−1,2,1,−5,4],
the contiguous subarray [4,−1,2,1] has the largest sum = 6.


自己最初想法:
需找最大字串的起點和終點。不是特別好解

分析:
答案:把原字符串分成很多不同的字串,然後求出字串中最大的

把原字符串分成很多不同的字串,通過max(f+A[i],A[i])就可以搞定,如果之前的對我沒貢獻,還不如另起一個字串

狀態爲 f[j],表示以 S[j] 結尾的最大連續子序列和,狀態轉移方程如下:

f=max(f+A[i],A[i]);//對於數組裏的一個整數,它只有兩種 選擇:1、加入之前的 SubArray;2. 自己另起一個 SubArray。

maxsum=max(maxsum,f);// 求字串中最大的

代碼:

class Solution {

public:

    int maxSubArray(int A[], int n) {

        if(0==n) return 0;

        int f=0;//f[j],表示以 A[j] 結尾的最大連續子序列和

        int maxsum=A[0];

        for(int i=0;i<n;++i)

        {

         

            f=max(f+A[i],A[i]);//是否需要另起一個字串,如果之前的對我沒貢獻,還不如另起一個字串。

            maxsum=max(maxsum,f); //字串中最大的

        }

        return maxsum;

    }

};


3.Palindrome Partitioning II

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

題意分析: 對輸入的字符串劃分爲一組迴文字符串,最小的分割次數


分析

定義狀態 f(i,j) 表示區間 [i,j] 之間最小的 cut 數,則狀態轉移方程爲

這是一個二維函數,實際寫代碼比較麻煩。

所以要轉換成一維 DP。如果每次,從 i 往右掃描,每找到一個迴文就算一次 DP 的話,就可以

轉換爲 f(i)= 區間 [i, n-1] 之間最小的 cut 數,n 爲字符串長度,則狀態轉移方程爲


一個問題出現了,就是如何判斷 [i,j] 是否是迴文?每次都從 i 到 j 比較一遍?太浪費了,這 裏也是一個 DP 問題。 

定義狀態 P[i][j] = true if [i,j] 爲迴文,那麼

P[i][j] = str[i] == str[j] && P[i+1][j-1]


代碼

// LeetCode, Palindrome Partitioning II

// 時間複雜度 O(n^2),空間複雜度 O(n^2)

class Solution {

public:

  int minCut(string s)

 {

    const int n = s.size();

    int f[n+1];

    bool p[n][n];

    fill_n(&p[0][0], n * n, false);

//the worst case is cutting by each char

    for (int i = 0; i <= n; i++)

            f[i] = n - 1 - i; // 最後一個 f[n]=-1

    for (int i = n - 1; i >= 0; i--)

    {

        for (int j = i; j < n; j++)

         {

            if (s[i] == s[j] && (j - i < 2 || p[i + 1][j - 1])) 

            { 

                    p[i][j] = true;

                    f[i] = min(f[i], f[j + 1] + 1);

           }

        }

    }

    return f[0];

}

}


4.Maximal Rectangle

描述

Given a 2D binary matrix filled with 0’s and 1’s, find the largest rectangle containing all ones and return

its area.

題目就是給一個矩陣,找一個全是一的最大子矩陣。


5.Best Time to Buy and Sell Stock III

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).


分析:

設狀態f(i)表示區間[0,i-1]上的最大利潤,設置狀態g(i),表示區間[i,n-1]上最大利潤。

則最大利潤爲max{f(i)+g(i)};允許在一天內買進又賣出,相當於不交易,因爲題目的規定是最多兩次,而不是一定要兩次。

代碼

// LeetCode, Best Time to Buy and Sell Stock III

// 時間複雜度 O(n),空間複雜度 O(n)

class Solution {

public:

int maxProfit(vector<int>& prices)

 {

    if (prices.size() < 2) return 0;

    const int n = prices.size();

    vector<int> f(n, 0);

    vector<int> g(n, 0);

    for (int i = 1, valley = prices[0]; i < n; ++i) {

        valley = min(valley, prices[i]);

        f[i] = max(f[i - 1], prices[i] - valley);

    }

    for (int i = n - 2, peak = prices[n - 1]; i >= 0; --i) {

        peak = max(peak, prices[i]);

        g[i] = max(g[i], peak - prices[i]);

    }

    int max_profit = 0;

    for (int i = 0; i < n; ++i)

        max_profit = max(max_profit, f[i] + g[i]);

    return max_profit;

    }

};


6.Interleaving String

Given s1s2s3, find whether s3 is formed by the interleaving of s1 and s2.

For example,
Given:
s1 = "aabcc",
s2 = "dbbca",

When s3 = "aadbbcbcac", return true.
When s3 = "aadbbbaccc", return false.

分析:判斷字符串s3是否由s1,s2交叉存取組成


設狀態 f[i][j],表示 s1[0,i] 和 s2[0,j],匹配 s3[0, i+j]。如果 s1 的最後一個字符等 於 s3 的最後一個字符,則

 f[i][j]=f[i-1][j];

如果 s2 的最後一個字符等於 s3 的最後一個字符, 則 

f[i][j]=f[i][j-1]。

因此狀態轉移方程如下: 

f[i][j] = (s1[i - 1] == s3 [i + j - 1] && f[i - 1][j]) || (s2[j - 1] == s3 [i + j - 1] && f[i][j - 1]);

1 class Solution {
 2 private:
 3     bool f[1000][1000];
 4 public:
 5     bool isInterleave(string s1, string s2, string s3) {
 6         // Start typing your C/C++ solution below
 7         // DO NOT write int main() function 
 8         if (s1.size() + s2.size() != s3.size())
 9             return false;
10             
11         f[0][0] = true;
12         for(int i = 1; i <= s1.size(); i++)
13             f[i][0] = f[i-1][0] && (s3[i-1] == s1[i-1]);
14             
15         for(int j = 1; j <= s2.size(); j++)
16             f[0][j] = f[0][j-1] && (s3[j-1] == s2[j-1]);
17             
18         for(int i = 1; i <= s1.size(); i++)
19             for(int j = 1; j <= s2.size(); j++)
20                 f[i][j] = (f[i][j-1] && s2[j-1] == s3[i+j-1]) || (f[i-1][j] && s1[i-1] == s3[i+j-1]);
21                 
22         return f[s1.size()][s2.size()];
23     }
24 };


動規 + 滾動數組

// LeetCode, Interleaving String

// 二維動規 + 滾動數組,時間複雜度 O(n^2),空間複雜度 O(n)

class Solution {

public:

bool isInterleave(string s1, string s2, string s3)

 {

    if (s1.length() + s2.length() != s3.length())

        return false;

    if (s1.length() < s2.length())

        return isInterleave(s2, s1, s3);

    vector<bool> f(s2.length() + 1, true);

    for (size_t i = 1; i <= s2.length(); ++i)

        f[i] = s2[i - 1] == s3[i - 1] && f[i - 1];

    for (size_t i = 1; i <= s1.length(); ++i)

     {

        f[0] = s1[i - 1] == s3[i - 1] && f[0];

        for (size_t j = 1; j <= s2.length(); ++j)

            f[j] = (s1[i - 1] == s3[i + j - 1] && f[j]) || (s2[j - 1] == s3[i + j - 1] && f[j - 1]); 

    }

    return f[s2.length()];

}

};


7.Scramble String(混雜字符串)

Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.

Below is one possible representation of s1 = "great":

    great
   /    \
  gr    eat
 / \    /  \
g   r  e   at
           / \
          a   t

To scramble the string, we may choose any non-leaf node and swap its two children.

For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".

    rgeat
   /    \
  rg    eat
 / \    /  \
r   g  e   at
           / \
          a   t

We say that "rgeat" is a scrambled string of "great".

Similarly, if we continue to swap the children of nodes "eat" and "at", it produces a scrambled string "rgtae".

    rgtae
   /    \
  rg    tae
 / \    /  \
r   g  ta  e
       / \
      t   a

We say that "rgtae" is a scrambled string of "great".

Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.



8.Minimum Path Sum

描述

Given a m n grid filled with non-negative numbers, find a path from top left to bottom right which

minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time

分析:

設狀態爲 f[i][j],表示從起點 (0; 0) 到達 (i; j ) 的最小路徑和,則狀態轉移方程爲:

f[i][j]=min(f[i-1][j], f[i][j-1])+grid[i][j]


代碼:

備忘錄法

// LeetCode, Minimum Path Sum

// 備忘錄法

class Solution

 {

public:

int minPathSum(vector<vector<int> > &grid)

 {

    const int m = grid.size();

    const int n = grid[0].size();

    this->f = vector<vector<int> >(m, vector<int>(n, -1));

    return dfs(grid, m-1, n-1);

}

private:

vector<vector<int> > f; // 緩存

int dfs(const vector<vector<int> > &grid, int x, int y)

 {

    if (x < 0 || y < 0) return INT_MAX; // 越界,終止條件,注意,不是 0

    if (x == 0 && y == 0) return grid[0][0]; // 回到起點,收斂條件

    return min(getOrUpdate(grid, x - 1, y),

    getOrUpdate(grid, x, y - 1)) + grid[x][y];

}

int getOrUpdate(const vector<vector<int> > &grid, int x, int y) 

{

    if (x < 0 || y < 0) 

            return INT_MAX; // 越界,注意,不是 0

    if (f[x][y] >= 0) 

            return f[x][y];

    else

         return f[x][y] = dfs(grid, x, y);

}

};

動規

// LeetCode, Minimum Path Sum

// 二維動規

class Solution

{

public:

int minPathSum(vector<vector<int> > &grid)

 {

    if (grid.size() == 0) return 0;

    const int m = grid.size();

    const int n = grid[0].size();

    int f[m][n];

    f[0][0] = grid[0][0];

    for (int i = 1; i < m; i++)

     {

        f[i][0] = f[i - 1][0] + grid[i][0];

    }

    for (int i = 1; i < n; i++)

     {

        f[0][i] = f[0][i - 1] + grid[0][i];

    }

    for (int i = 1; i < m; i++) 

    {

        for (int j = 1; j < n; j++)

         {

            f[i][j] = min(f[i - 1][j], f[i][j - 1]) + grid[i][j];

        }

    }

   return f[m - 1][n - 1];

}

};

動規 + 滾動數組

// LeetCode, Minimum Path Sum

// 二維動規 + 滾動數組

class Solution {

public:

int minPathSum(vector<vector<int> > &grid)

 {

        const int m = grid.size();

        const int n = grid[0].size();

        int f[n];

        fill(f, f+n, INT_MAX); // 初始值是 INT_MAX,因爲後面用了 min 函數。

        f[0] = 0;

        for (int i = 0; i < m; i++)

         {

            f[0] += grid[i][0];

            for (int j = 1; j < n; j++) 

            {

                    // 左邊的 f[j],表示更新後的 f[j],與公式中的 f[i[[j] 對應

                    // 右邊的 f[j],表示老的 f[j],與公式中的 f[i-1][j] 對應

                    f[j] = min(f[j - 1], f[j]) + grid[i][j];

               }

            }

    return f[n - 1];

}

};


9 Edit Distance

描述

Given two words word1 and word2, find the minimum number of steps required to convert word1 to

word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

• Insert a character

• Delete a character

• Replace a character


分析

設狀態爲 f[i][j],表示 A[0,i] 和 B[0,j] 之間的最小編輯距離。設 A[0,i] 的形式是

str1c,B[0,j] 的形式是 str2d,

1. 如果 c==d,則 f[i][j]=f[i-1][j-1];

2. 如果 c!=d,

(a) 如果將 c 替換成 d,則 f[i][j]=f[i-1][j-1]+1;

(b) 如果在 c 後面添加一個 d,則 f[i][j]=f[i][j-1]+1;

(c) 如果將 c 刪除,則 f[i][j]=f[i-1][j]+1;

動規

// LeetCode, Edit Distance

// 二維動規,時間複雜度 O(n*m),空間複雜度 O(n*m)

class Solution {

public:

int minDistance(const string &word1, const string &word2) {

    const size_t n = word1.size();

    const size_t m = word2.size();

    // 長度爲 n 的字符串,有 n+1 個隔板

    int f[n + 1][m + 1];

    for (size_t i = 0; i <= n; i++)

        f[i][0] = i;

    for (size_t j = 0; j <= m; j++)

        f[0][j] = j;

    for (size_t i = 1; i <= n; i++) {

        for (size_t j = 1; j <= m; j++) {

            if (word1[i - 1] == word2[j - 1])

                f[i][j] = f[i - 1][j - 1];

            else {

                int mn = min(f[i - 1][j], f[i][j - 1]);

                f[i][j] = 1 + min(f[i - 1][j - 1], mn);

            }

        }

}

return f[n][m];

}

};


動規 + 滾動數組

// LeetCode, Edit Distance

// 二維動規 + 滾動數組

// 時間複雜度 O(n*m),空間複雜度 O(n)

class Solution {

public:

int minDistance(const string &word1, const string &word2) {

    if (word1.length() < word2.length())

        return minDistance(word2, word1);

    int f[word2.length() + 1];

    int upper_left = 0; // 額外用一個變量記錄 f[i-1][j-1]

    for (size_t i = 0; i <= word2.size(); ++i)

        f[i] = i;

    for (size_t i = 1; i <= word1.size(); ++i) {

        upper_left = f[0];

        f[0] = i;

        for (size_t j = 1; j <= word2.size(); ++j) {

            int upper = f[j];

            if (word1[i - 1] == word2[j - 1])

                f[j] = upper_left;

            else

                f[j] = 1 + min(upper_left, min(f[j], f[j - 1]));

            upper_left = upper;

    }

}

return f[word2.length()];

}

};


10 Decode Ways

描述

A message containing letters from A-Z is being encoded to numbers using the following mapping:

'A' -> 1

'B' -> 2

...

'Z' -> 26

Given an encoded message containing digits, determine the total number of ways to decode it.

For example, Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).

The number of ways decoding "12" is 2


分析 :

和爬樓梯問題一樣,

設 f (n) 表示爬 n 階樓梯的不同方法數,爲了爬到第 n 階樓梯,有兩個選擇:

• 從第 n-1 階前進 1 步;

• 從第 n-2 階前進 2 步;

因此,有 f (n) = f (n-1) + f (n-2)。 這是一個斐波那契數列。


這裏也一樣,多一些約束條件而已。判斷兩個數字時,是否小於26

代碼

// LeetCode, Decode Ways

// 動規,時間複雜度 O(n),空間複雜度 O(1)+滾動數組

class Solution {

public:

int numDecodings(const string &s) {

    if (s.empty() || s[0] == '0') return 0;

    int prev = 0;//f(0)=0

    int cur = 1;//f(1)=1

    // 長度爲 n 的字符串,有 n+1 個階梯

    for (size_t i = 1; i <= s.size(); ++i) {

        if (s[i-1] == '0')

             cur = 0;

        if (i < 2 || !(s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))) 

            prev = 0;

        int tmp = cur;

        cur = prev + cur;//f(i)=f(i-2)+f(i-1)

        prev = tmp;

    }

    return cur;

}

};


11 Distinct Subsequences(不同的子序列)


描述
Given a string S and a string T , count the number of distinct subsequences of T in S.
A subsequence of a string is a new string which is formed from the original string by deleting some (can  be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not). 
Here is an example: S = "rabbbit", T = "rabbit"
Return 3.

給定2個字符串a, b,求b在a中出現的次數。要求可以是不連續的,但是b在a中的順序必須和b以前的一致。 

分析

類似於數字分解的題目。dp[i][j]表示:b的前j個字符在a的前i個字符中出現的次數


如果a[i]==b[j],那麼dp[i][j] = dp[i-1][j-1] + dp[i-1][j]。
意思是:如果當前a[i]==b[j],那麼當前這個字母即可以保留也可以拋棄,所以變換方法等於保留這個字母的變換方法加上不用這個字母的變換方法。
如果a[i]!=b[i],那麼dp[i][j] = dp[i-1][j]
意思是如果當前字符不等,那麼就只能拋棄當前這個字符

遞歸公式中用到的dp[0][0] = 1,dp[i][0] = 0(把任意一個字符串變換爲一個空串只有一個方法


代碼

// LeetCode, Distinct Subsequences

// 二維動規 + 滾動數組

// 時間複雜度 O(m*n),空間複雜度 O(n)

class Solution {

public:

int numDistinct(const string &S, const string &T) {

    vector<int> f(T.size() + 1);

    f[0] = 1;

    for (int i = 0; i < S.size(); ++i) {

        for (int j = T.size() - 1; j >= 0; --j) {

        f[j + 1] += S[i] == T[j] ? f[j] : 0;

        }

    }

    return f[T.size()];

}

};


12 Word Break

描述

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated

sequence of one or more dictionary words.

For example, given

s = "leetcode",

dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".


分析:

dp[i]  表示源串的前i個字符可以滿足分割,那麼 dp[ j ] 滿足分割的條件是存在k 使得 dp [k] && substr[k,j]在字典裏。
  1. class Solution {  
  2. public:  
  3.     bool wordBreak(string s, unordered_set<string> &dict) {  
  4.         // Note: The Solution object is instantiated only once and is reused by each test case.  
  5.         int n = (int)s.size();  
  6.         vector<bool> dp(n + 1, false);  
  7.         dp[0]=true;  
  8.         for(int i=1;i<=n;i++)  
  9.         {  
  10.             if(dp[i-1])  
  11.             {  
  12.                 int idx=i-1;  
  13.                 for(int j=idx;j<n;j++)  
  14.                 {  
  15.                     string cur=s.substr(idx,j-idx+1);  
  16.                     if(dict.count(cur)>0)  
  17.                         dp[j+1]=true;  
  18.                 }  
  19.             }  
  20.         }  
  21.         return dp[n];  
  22.     }  
  23. };  
13.Word Break II

Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.

Return all such possible sentences.

For example, given
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].

A solution is ["cats and dog", "cat sand dog"].


跟第一題一樣,就是要返回所有可能的切分, 做切分反應是用回溯,但是不剪枝肯定要超時。

這裏用了一個math[i][j] 來表示 i--j 這一段是否可以切分,然後在dfs的時候利用看最後剩餘的子串能否切分來剪枝


  1. class Solution {  
  2. public:  
  3.     vector<string> wordBreak(string s, unordered_set<string> &dict)   
  4.     {  
  5.         int n=s.length();  
  6.         vector<vector<bool> > match(n+1,vector<bool>(n+1,false));  
  7.         for(int i=0;i<=n;i++)  
  8.             match[0][i]=true;  
  9.         for(int len=1;len<=n;len++)  
  10.         {  
  11.             for(int start=0;start+len<=n;start++)  
  12.             {  
  13.                 string tmp=s.substr(start,len);  
  14.                 if(dict.count(tmp)>0)  
  15.                     match[len][start]=true;  
  16.                 else  
  17.                 {  
  18.                     for(int left=1;left<len;left++)  
  19.                     {  
  20.                         match[len][start]=match[left][start]&&match[len-left][start+left];  
  21.                         if(match[len][start])  
  22.                             break;  
  23.                     }  
  24.                 }  
  25.             }  
  26.         }  
  27.         if(match[n][0]==false)  
  28.             return vector<string>();  
  29.         vector<string> ans;  
  30.         vector<string> had;  
  31.         dfs(s,0,match,had,ans,dict);  
  32.         return ans;  
  33.     }  
  34.     void dfs(string& s,int k,vector<vector<bool> >& match,vector<string>& had,vector<string>& ans,unordered_set<string> &dict)  
  35.     {  
  36.         int n=s.length();  
  37.         if(k>=n)  
  38.         {  
  39.             if(!had.empty())  
  40.             {  
  41.                 string ret;  
  42.                 for(int i=0;i<had.size();i++)  
  43.                 {  
  44.                     ret.append(had[i]);  
  45.                     if(i!=had.size()-1)  
  46.                         ret.push_back(' ');  
  47.                 }  
  48.                 ans.push_back(ret);  
  49.                 return;  
  50.             }  
  51.         }  
  52.         for(int len=1;k+len<=n;len++)  
  53.         {  
  54.             string tmp=s.substr(k,len);  
  55.             if(dict.count(tmp)>0 && match[n-k-len][k+len])  
  56.             {  
  57.                 had.push_back(tmp);  
  58.                 dfs(s,k+len,match,had,ans,dict);  
  59.                 had.pop_back();  
  60.             }  
  61.         }  
  62.     }     
  63. };  
發佈了39 篇原創文章 · 獲贊 71 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章