【动归】最长×××解法模板

子序列问题可能会比子串、子数组困难一些,因为前者是不连续的序列,而后者是连续的,所以这里总结一下两种序列的不同做法,以及简单的区别。相关问题只需要往这两种思路上想,十拿九稳。

一、最长×××子序列(非连续)

复杂度

  • 时间复杂度:
    • 双串: O ( n 2 ) O(n^2) ,二维枚举。
    • 单串: O ( n 1 ) O(n^1) ,一维枚举。
  • 空间复杂度: O ( n ) O(n) ,一般涉及到 递增/递减/最长 这些只需一维 dp 即可。

代码模板

int[] dp = new int[N];
for (从 i 到 N)
for (从 j 到 i) {
  if (条件判断)
    dp[i] = max(dp[i], dp[j] + x);
// else ...
}

「单串」例题

最长上升子序列
for (int i = 1; i < N; i++)
 for (int j = 0; j < i; j++) {
   if (nums[j] < nums[i])
     dp[i] = Math.max(dp[i], dp[j] + 1);
 }
最长连续上升子序列
for (int i = 1; i < N; i++) {
  if (nums[i-1] < nums[i])
     dp[i] = dp[i-1] + 1;
   else
     dp[i] = 1;
 }

有时候还会涉及到 else 语句,比如最长定差子序列:

最长定差子序列
for (int i : arr) {
  int j = i - difference;
  if (j >= 0 && j <= 20000) dp[i] = dp[j] + 1;
  else                      dp[i] = 1;
  len = Math.max(dp[i], len);
}

但有的题目用 dp 解真的很方便:比如求某些序列是否存指定子序列

递增的三元子序列
for (int i = 1; i < N; i++)
for (int j = 0; j < i; j++) {
  if (nums[j] < nums[i])
    dp[i] = Math.max(dp[i], dp[j] + 1);
  if (dp[i] >= 3)
    return true;
}

「双串」例题

最长公共子序列
for (int p1 = 1; p1 <= len1; p1++)
for (int p2 = 1; p2 <= len2; p2++) {
  if (s1[p1-1] == s2[p2-1])
    dp[p1][p2] = dp[p1-1][p2-1] + 1;
  else
    dp[p1][p2] = Math.max(dp[p1-1][p2], dp[p1][p2-1]);
}
最长回文子序列

解法一:转换思维,将字符串 s 逆序得到新串,问题转化为:在比较 s1 与 s2 两个字符串的最长公共子序列,但效率比较低。

for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++) {
  if (s1[i-1] == s2[j-1])
    dp[i][j] = dp[i-1][j-1] + 1;
  else
    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
return dp[N][N];

第二种解法就是每次循环通过缩减问题规模达到加速效果。

for (int i = 0; i < N; i++) 
	dp[i][i] = 1;
for (int i = N-1; i >= 0; i--)
for (int j = i+1; j < N; j++) {
  if (S[i] == S[j]) dp[i][j] = dp[i+1][j-1] + 2;
  else              dp[i][j] = Math.max(dp[i][j-1], dp[i+1][j]);
}

二、最长「子串」或「子数组」

这一类题目相对比较简单,常见的有:

「单串」例题

最大子序和

涉及到数组这个关键字,一般都是求连续的序列。

for (int i = 1; i < N; i++) {
  dp[i] = Math.max(dp[i-1] + nums[i], dp[i] + nums[i]);
  max = Math.max(max, dp[i]);
}
最长摆动序列

这道题的细节很多,这里只给出核心部分。

for (int i = 1; i <= len; i++) {
   if (diff[i] * diff[i-1] < 0)
     dp[i] = dp[i-1] + 1;
   else
     dp[i] = dp[i-1];
 }
乘积最大子序列

这道题稍有另类,因为涉及到负数,负数会把正的乘积变小,会把负的乘积变大。

for (int i = 1; i < N; i++) {
  dp[i][0] = Math.max(Math.max(dp[i-1][0] * nums[i], nums[i]), dp[i][1] * nums[i]);
  dp[i][1] = Math.min(Math.min(dp[i-1][0] * nums[i], nums[i]), dp[i][1] * nums[i]);
  if (dp[i][0] > max)
    max = dp[i][0];
}

「双串」例题

最长重复子数组

和这题相同解法的有最长重复子串,解法一模一样。

for (int i = 1; i <= N1; i++)
for (int j = 1; j <= N2; j++) {
  if (A[i-1] == B[j-1]) {
    dp[i][j] = dp[i-1][j-1] + 1;
    maxLen = Math.max(maxLen  dp[i][j]);
  }
}

参考链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence/solution/zi-xu-lie-wen-ti-tong-yong-si-lu-zui-chang-hui-wen/

发布了461 篇原创文章 · 获赞 102 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/104722057