子序列问题可能会比子串、子数组困难一些,因为前者是不连续的序列,而后者是连续的,所以这里总结一下两种序列的不同做法,以及简单的区别。相关问题只需要往这两种思路上想,十拿九稳。
一、最长×××子序列(非连续)
复杂度
- 时间复杂度:
- 双串: ,二维枚举。
- 单串: ,一维枚举。
- 空间复杂度: ,一般涉及到 递增/递减/最长 这些只需一维 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/