1、输出长度
问题描述:
一个数的序列bi,当b12<…S的时候,我们称这个序列是上升的。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。 你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入的第一行是序列的长度N(1≤N≤1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出
最长上升子序列的长度。
样例输入
7
1 7 3 5 9 4 8
样例输出
4
策略分析:
复杂度O(n2):O(n2)的策略也是现在最常见的,首先考虑状态的设计,假设dp[i]表示第i个元素为结尾的最长上升子序列,很明显当前的状态只能由之前的状态得到,我们就要考虑寻找i之前的元素,假设i之前的元素由j表示,那么应该找到a[i]>a[j](因为是上升序列)并且dp[j]要是前面序列中最大的才行。代码
#include <cstdio>
#define MAXN 1005
int a[MAXN], dp[MAXN]; //dp[i]表示以第i个元素结尾的最长子序列
int main() {
int n, mx = 1; //注意mx一定要初始化为1,最长子序列至少都是1
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
dp[i] = 1; //初始化为1,每个元素的最长子序列至少都是1
}
for(int i = 1; i <= n; i++)
for(int j = 1; j < i; j++)
if(a[i] > a[j] && dp[i] <= dp[j]) {
dp[i] = dp[j]+1;
if(dp[i] > mx) //填充数组的时候不断更新最大值即可
mx = dp[i];
}
printf("%d\n", mx);
return 0;
}
二、输出路径
现在要求输出这个最长子序列的路径
策略分析:
由于是输出序列,我们在更新dp数组的时候是根据之前的状态更新,那我们很容易想到,每次更新的时候要纪录一下从哪一个地方更新过来的,因此可以使用一个pre数组进行纪录,然后使用递归的方式向前找即可
pre[i]表示第i个元素的前一个元素是pre[i]
代码
#include <cstdio>
#define MAXN 10020
int a[MAXN], dp[MAXN], pre[MAXN];
void print(int x) {
if(x == pre[x]) { //如果找到尽头了
printf("%d ", a[x]);
return;
}
print(pre[x]);
printf("%d ", a[x]);
}
int main() {
int n, mx = 1, loc = 1;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
dp[i] = 1; //初始化为1,每个元素的最长子序列至少都是1
pre[i] = i; //初始化,每个元素的前缀都是自己本身
}
for(int i = 1; i <= n; i++) //实际上i可以从2开始
for(int j = 1; j < i; j++)
if(a[i] > a[j] && dp[i] <= dp[j]) {
dp[i] = dp[j]+1;
pre[i] = j; //记录当前位置的前缀
if(dp[i] > mx) { //记录一下最长序列出现的位置 ,方便递归
mx = dp[i];
loc = i;
}
}
printf("%d\n", mx);
print(loc); //递归输出序列
return 0;
}
1、输出长度
问题描述:
给出两个字符串,求最长的公共子序列(子序列就是在该序列中删去若干元素后得到的序列,可以不连续)
输入
共有两行。每行为一个由大写字母构成的长度不超过1000的字符串,表示序列X和Y。
输出
第一行为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出文件仅有一行输出一个整数0。
样例输入
ABCBDAB
BDCABA
样例输出
4
策略分析:
复杂度O(n2):我们可以设计如下状态 dp[i][j]表示第一个序列中前i个和第二个序列中前j个的最长公共子序列,那么假如出现了,a[i-1] = b[j-1]的情况,很显然,可以更新dp[i][j] = max(dp[i][j],d[i-1][j-1]+1),如果没有相同的公共元素,当前的状态取决于选取之前的最优值,那么dp[i][j] = max(dp[i-1][j], dp[i][j-1])代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 1005
using namespace std;
char a[MAXN], b[MAXN];
int dp[MAXN][MAXN];
int main() {
int len1, len2;
scanf("%s%s", a, b);
len1 = strlen(a);
len2 = strlen(b);
for(int i = 0; i <= len1; i++) { //由于dp[i][j]表示的是i之前的元素的状态,因此要循环到len1
for(int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) continue;
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]);
}
}
printf("%d\n", dp[len1][len2]); //输出结果
return 0;
}
输出长度
问题描述:
给出两个序列,求最长上升公共子序列(子序列就是在该序列中删去若干元素后得到的序列,可以不连续)
输入
每个序列用两行表示,第一行是长度M(1 <= M <= 500),第二行是该序列的M个整数Ai (-231 <= Ai < 231 )
输出
在第一行,输出两个序列的最长上升公共子序列的长度L。在第二行,输出该子序列。如果有不止一个符合条件的子序列,则输出任何一个即可。
样例输入
5
1 4 2 5 -12
4
-12 1 2 4
样例输出
2
1 4
策略分析:
首先定义状态d[i][j]表示第一个序列中前i个元素和第二个序列的前j个元素并且以b[j]为结尾的LCIS代码(O(n*m2))
#include <cstdio>
#define MAXN 505
int d[MAXN][MAXN], a[MAXN], b[MAXN];
int main() {
int n, m;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
scanf("%d", &m);
for(int i = 1; i <= m; i++)
scanf("%d", &b[i]);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
d[i][j] = d[i-1][j];
if(a[i] == b[j]) {
int mx = -1;
for(int k = 1; k < j; k++)
if(b[k] < b[j] && mx < d[i-1][k])
mx = d[i-1][k];
d[i][j] = mx+1;
}
}
}
int ans = 0;
for(int i = 1; i <= m; i++)
if(ans < d[n][i])
ans = d[n][i];
printf("%d\n", ans);
}
不难发现,对于之前的代码,复杂度出现(O(n*m2))的情况,必然是因为当a[i]==b[j]的时候,我们去枚举了从1~j-1的情况,其实我们可以预先将d[x-1][k]的值保存下来,我们思考如何保存,假如出现了情况a[i]==b[j],我们之前找j之前的所有元素,从这里发现a[i] > b[k]的,那么对于之前的每一次遍历,只要满足a[i] > b[j]这个条件,我们都可以更新一下,最大值mx = max(mx,d[i-1][j]),这样就提前找到了d[i-1][j]之前的最大的情况,变成了(O(n*m))
代码(O(n*m))
#include <cstdio>
#define MAXN 505
int d[MAXN][MAXN], a[MAXN], b[MAXN];
int main() {
int n, m, mx;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
scanf("%d", &m);
for(int i = 1; i <= m; i++)
scanf("%d", &b[i]);
for(int i = 1; i <= n; i++) {
mx = 0;
for(int j = 1; j <= m; j++) {
d[i][j] = d[i-1][j];
if(a[i] > b[j] && mx < d[i-1][j]) mx = d[i-1][j];
if(a[i] == b[j]) d[i][j] = mx + 1;
}
}
int ans = 0;
for(int i = 1; i <= m; i++)
if(ans < d[n][i])
ans = d[n][i];
printf("%d\n", ans);
}
同时还可以将dp数组优化成一维的,因为如果a[i]!=b[j],那么当前不会发生任何变化继承之前的即可,只需要改变a[i]==b[j]的时候,d[j]的值即可,最后遍历d数组,找最长即可
#include <cstdio>
#define MAXN 505
int d[MAXN], a[MAXN], b[MAXN];
int main() {
int n, m, mx;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
scanf("%d", &m);
for(int i = 1; i <= m; i++)
scanf("%d", &b[i]);
for(int i = 1; i <= n; i++) {
mx = 0;
for(int j = 1; j <= m; j++) {
if(a[i] > b[j] && mx < d[j]) mx = d[j];
if(a[i] == b[j]) d[j] = mx + 1;
}
}
int ans = 0;
for(int i = 1; i <= m; i++)
if(ans < d[i])
ans = d[i];
printf("%d\n", ans);
}
参考:https://wenku.baidu.com/view/3e78f223aaea998fcc220ea0.html