Basics on Dynamic Programming(DP) 4 - Two Sequence DP

第3类DP问题:Two Sequence DP,2个序列相互关系

1. Longest Common Subsequence LCS - lintcode

Given two strings, find the longest comment subsequence (LCS).
Your code should return the length of LCS.

Example:
For "ABCD" and "EDCA", the LCS is "A" (or D or C), return 1
For "ABCD" and "EACB", the LCS is "AC", return 2
  • state: f[i][j] // 第一个字符串的前i个字符匹配上第二个字符串的前j个字符,所能找到的LCS的长度是什么
  • function: f[i][j] = {f[i-1][j-1] + 1 (a[i] == b[j])} or {max(f[i-1][j], f[i][j-1]) (a[i] != b[j])}
  • intialize: f[0][i] = 0, f[i][0] = 0 // 类似二维matrix的,初始化两边
  • answer: f[a.length()][b.length()]
int longestCommonSubsequence(string A, string B) {
  int m = A.size(), n = B.size();
  // initialize with 0,因为边界,多一个, 表示前0个字符
  vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
      if (A[i - 1] == B[j - 1])
        dp[i][j] = dp[i - 1][j - 1] + 1;
      else
        dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
    }
  }
  return dp[m][n];
}

压缩后,注意!不是只跟i-1有关, 需要变量保存i-1,j-1

int longestCommonSubsequence(string A, string B) {
  int m = A.size(), n = B.size();
  vector<int> dp(n + 1, 0);  // initialize with 0
  for (int i = 1; i <= m; i++) {
    int lastDp = 0;  // !!注意清空0,否则还是上一行的最后一个
    for (int j = 1; j <= n; j++) {
      int temp = dp[j];
      if (A[i - 1] == B[j - 1])
        dp[j] = lastDp + 1;
      else
        dp[j] = max(dp[j - 1], dp[j]);  // j只能从小到大,j依赖于j-1
      // dp[j]相当于dp[i-1][j], 而dp[j-1]相当于dp[i][j-1]
      lastDp = temp;
    }
  }
  return dp[n];
}

变体,连续子序列, Longest Common Substring

int longestCommonSubstring(string &A, string &B) {
  int m = A.size();
  int n = B.size();
  int maxLen = 0;
  vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));  //初始化,都是0
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
      // 这里不同,为0
      dp[i][j] = (A[i - 1] != B[j - 1]) ? 0 : (dp[i - 1][j - 1] + 1);
      maxLen = maxLen < dp[i][j] ? dp[i][j] : maxLen;  // 这里不同,一直保存max
    }
  }
  return maxLen;
}

压缩后:

int longestCommonSubstring(string &A, string &B) {
  int m = A.size();
  int n = B.size();
  int maxLen = 0;
  vector<int> dp(n + 1, 0);  //初始化,都是0
  for (int i = 1; i <= m; i++) {
    int lastDP = 0;
    for (int j = 1; j <= n; j++) {
      int tmp = dp[j];
      dp[j] = (A[i - 1] != B[j - 1]) ? 0 : (lastDP + 1); // ! 注意这里不是dp[j-1]
      maxLen = maxLen < dp[j] ? dp[j] : maxLen;  // 这里不同,一直保存max
      lastDP = tmp;
    }
  }
  return maxLen;
}

2. Edit Distance - Leetcode 72

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:
a) Insert a character
b) Delete a character
c) Replace a character
  • state: f[i][j] 第一个字符串的前i个字符匹配上第二个字符串的前j个字符,最少编辑距离
  • function: f[i][j] = {f[i-1][j-1] (a[i] == b[j])} or {min(f[i-1][j], f[i][j-1], f[i-1][j-1]) + 1 (a[i] != b[j])}
  • intialize: f[0][i] = i, f[i][0] = i
  • answer: f[a.length()][b.length()]
int minDistance(string word1, string word2) {
  int m = word1.size(), n = word2.size();
  vector<vector<int> > dp(m + 1, vector<int>(n + 1, INT_MAX));
  for (int i = 0; i <= m; i++) dp[i][0] = i;
  for (int i = 0; i <= n; i++) dp[0][i] = i;
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
      if (word1[i - 1] == word2[j - 1])
        dp[i][j] = dp[i - 1][j - 1];
      else
        dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
    }
  }
  return dp[m][n];
}

压缩一维: 注意保存i-1 j-1

int minDistance(string word1, string word2) {
  int m = word1.size(), n = word2.size();
  vector<int> dp(n + 1, INT_MAX);
  for (int i = 0; i <= n; i++) dp[i] = i;
  for (int i = 1; i <= m; i++) {
    int lastDP = dp[0];  // 这两句顺序不能搞错,先记录lastDP(上一轮的)!!
    dp[0] = i;
    for (int j = 1; j <= n; j++) {
      int tmp = dp[j];
      if (word1[i - 1] == word2[j - 1])
        dp[j] = lastDP;
      else
        dp[j] = min(min(dp[j], dp[j - 1]), lastDP) + 1;
      lastDP = tmp;
    }
  }
  return dp[n];
}

3. Interleaving String - Leetcode 97

Given s1, s2, s3, 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.
  • state: f[i][j]第一个字符串的前i个字符,匹配上第二个字符串的前j个字符,能否交错构成第三个字符串的前i+j个字符
  • function: f[i][j] = f[i-1][j] (if a[i] == c[i+j]) or f[i][j-1] (if b[j] == c[i+j])
  • intialize: f[i][0]={a的前i个字符==c的前i个字符}; f[0][i]=同理可得
  • answer: f[a.length()][b.length()]
bool isInterleave(string s1, string s2, string s3) {
  if (s3.length() != s1.length() + s2.length())  //直接错误
    return false;
  int m = s1.length(), n = s2.length();
  vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
  //初始化,2条边
  dp[0][0] = true;
  for (int i = 1; i <= m; i++) {
    if (s1[i - 1] == s3[i - 1])  dp[i][0] = true;
    else  break;  //只要有1个不等于,就break
  }
  for (int i = 1; i <= n; i++) {
    if (s2[i - 1] == s3[i - 1])  dp[0][i] = true;
    else  break;  //只要有1个不等于,就break
  }
  //开始算
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++)
      dp[i][j] = ((s1[i - 1] == s3[i + j - 1]) && dp[i - 1][j]) ||
                 ((s2[j - 1] == s3[i + j - 1]) && dp[i][j - 1]);
  }
  return dp[m][n];
}

压缩:

bool isInterleave(string s1, string s2, string s3) {
  if (s3.length() != s1.length() + s2.length())  //直接错误
    return false;
  int m = s1.length(), n = s2.length();
  vector<bool> dp(n + 1, false);
  //初始化,2条边
  dp[0] = true;
  for (int i = 1; i <= n; i++) {
    if (s2[i - 1] == s3[i - 1])  dp[i] = true;
    else  break;  //只要有1个不等于,就break
  }
  //开始算
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++)
      dp[j] = ((s1[i - 1] == s3[i + j - 1]) && dp[j]) ||
              ((s2[j - 1] == s3[i + j - 1]) && dp[j - 1]);
  }
  return dp[n];
}

4. Distinct Subsequence - Leetcode115 Hard

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.
  • state: f[i][j]第一个字符串的前i个字符,匹配上第二个字符串的前j个字符,有多少个distinct subseqs
  • function: f[i][j] = f[i-1][j] + f[i-1][j-1] (if a[i] == b[j]) or f[i-1][j] (if a[i] != b[j],没有删除j的这种情况了)
  • intialize: f[i][0]=1, f[0][i] = 0 (i>0)
  • answer: f[a.length()][b.length()]

求方案总数,用DP,dfs做,大数据会超时

int numDistinct(string &S, string &T) {
  int M = S.size(), N = T.size();
  if (M == 0 && N == 0)  return 1;
  else if (M < N)  return 0;

  vector<vector<int> > dp(M + 1, vector<int>(N + 1, 0));
  //初始化
  for (int i = 0; i <= M; i++) dp[i][0] = 1;
  for (int i = 1; i <= N; i++) dp[0][i] = 0;  // 0,0是1!!
  for (int i = 1; i <= M; i++) {
    for (int j = 1; j <= N; j++) {
      if (S[i - 1] == T[j - 1])  //若 S[i]==T[j],则可以使用 S[i],是二者的和
        dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
      else
        dp[i][j] = dp[i - 1][j];  //不能使用S[i],则等于i-1到j的种类数
    }
  }
  return dp[M][N];
}

压缩一维:

int numDistinct(string &S, string &T) {
  int M = S.size(), N = T.size();
  if (M == 0 && N == 0)  return 1;
  else if (M < N)  return 0;

  vector<int> dp(N + 1, 0);
  dp[0] = 1;  // 初始化  0,0是1!!
  for (int i = 1; i <= M; i++) {
    int lastDP = 1;
    for (int j = 1; j <= N; j++) {
      int tmp = dp[j];
      if (S[i - 1] == T[j - 1])  //若 S[i]==T[j],则可以使用 S[i],是二者的和
        dp[j] = dp[j] + lastDP;
      lastDP = tmp;
    }
  }
  return dp[N];
}
// 进一步优化,倒着来
int numDistinct(string S, string T) {
  int M = S.size(), N = T.size();
  if (M == 0 && N == 0)  return 1;
  else if (M < N)  return 0;
  vector<int> dp(N + 1, 0);
  dp[0] = 1;
  for (int i = 1; i <= M; i++) {
    for (int j = N; j >= 1; j--) {  //压缩后,从后往前,避免覆盖
      if (S[i - 1] == T[j - 1])  //若 S[i]==T[j],则可以使用 S[i],是二者的和
        dp[j] = dp[j - 1] + dp[j];  // 区别,用的是上一次的j-1,不需要先算这次的j-1
    }
  }
  return dp[N];
}
Loading Disqus comments...
Table of Contents