高效面试之动态规划DP

解题关键:

理解结构特征,抽象出状态,写成状态转移方程。


题目索引

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. };  


你可能感兴趣的:(dp,动态规划,高效面试)