1.最大子序和
常量空间线性时间的联机算法几乎是完美的算法。
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
void getlen(int a[],int n){
int tt=-99999;
int start=0;//起始位置
int end=0;//结束位置
int sum=0;
int k=0;
for(int i=0;i<n;i++)
{
sum+=a[i];
if(tt<sum)
{
tt=sum;
end=i;
start=k;
}
if(sum<0)
{
sum=0;
k=i+1;
}
}
printf("%d %d~%d\n",tt,start,end);
}
int main()
{
int len;
int a[10];
cin>>len;
for(int i=0;i<len;i++)
cin>>a[i];
getlen(a,len);
return 0;
}
2.最长公共子序列(可不连续)LCS
//最长公共子序列(可不连续)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int dp[105][105]; //记录当前字母“前面”的最长子序列的长度
//char a[100],b[100];
int path[150];
int main()
{
char a[20]="bcbcbab";
char b[20]="cccabcc";
//cin>>a>>b;
int len1 = strlen(a);
int len2 = strlen(b);
memset(dp,0,sizeof(dp));
for(int i = 1; i <= len1; i++) //i,j从一开始
for(int j = 1; j <= len2; j++)
{
if(a[i-1] == b[j-1]) //前一个相同,当前的就是前面dp+1;
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
cout << dp[len1][len2] << endl;
for(int i=0;i<=len1;i++)
{
for(int j=0;j<=len2;j++)
printf("%d ",dp[i][j]);
printf("\n");
}
//输出路径,只会输其中一条(如果有多条路径的话);从a字符串里面输出。
//关于为什么这么输出,可以参考上面输出的dp数组,可加深对LCS求解过程的理解
int i = len1, j = len2, k = dp[len1][len2]; //k代表最长的数量;ij都是从后往前找;
while(i > 0)
{
if(dp[i][j] == dp[i-1][j])
i--;
else if(dp[i][j] == dp[i][j-1])
j--;
else
{
path[k--] = i-1;
i--,j--;
}
}
for(int i = 1; i <= dp[len1][len2]; i++) //从前往后输出
{
printf("%c",a[path[i]]);
}
printf("\n");
//输出路径,只会输其中一条(如果有多条路径的话);从b字符串里面输出
i = len1, j = len2, k = dp[len1][len2];
while(j > 0)
{
if(dp[i][j] == dp[i][j-1])
j--;
else if(dp[i][j] == dp[i][j-1])
i--;
else
{
path[k--] = j-1;
i--,j--;
}
}
for(int i = 1; i <= dp[len1][len2]; i++)
{
printf("%c",b[path[i]]);
}
printf("\n");
return 0;
}
3.最长公共子串(连续)
[cpp] view plain copy
1. #include <iostream>
2. #include <cstdio>
3. #include <cstring>
4. using namespace std;
5. int dp[105][105]; //记录当前字母“前面”的最长子序列的长度
6. char a[100], b[100];
7. int main()
8. {
9. while(cin >> a >> b)
10. {
11. int max1 = 0, temp;
12. memset(dp,0,sizeof(dp));
13. int len1 = strlen(a), len2 = strlen(b);
14. for(int i = 1; i <= len1; i++)
15. for(int j = 1; j <= len2; j++)
16. {
17. if(a[i-1] == b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
18. else dp[i][j] = 0;
19. if(max1 < dp[i][j])
20. {
21. max1 = dp[i][j]; //纪录dp[][]中的最大值
22. temp = i;//纪录最长公共子串的末端在str1中的位置(也可以纪录在str2中的位置)
23. }
24. }
25. for(int i = temp - max1; i < temp; i++)
26. cout << a[i];
27. cout << endl;
28. }
29.
30. }
4.LIS最长上升子序列的三种写法
方法一:转换成LCS问题 空间时间复杂度都比较大, 空间n^2,时间n^2+nlogn
将原数组与排好序的数组进行求LCS,得到的最长公共子序列的就是最长上子序列.
[cpp] view plain copy
1. #include<cstdio>
2. #include<cstring>
3. #include<iostream>
4. #include<algorithm>
5. using namespace std;
6. const int maxn = 10005;
7. int a[maxn], b[maxn], dp[maxn][maxn];
8. int main(void)
9. {
10. int n;
11. while(cin >> n)
12. {
13. memset(dp, 0, sizeof(dp));
14. for(int i = 0; i < n; i++) scanf("%d", &a[i]), b[i] = a[i];
15. sort(b, b+n);
16. for(int i = 1; i <= n; i++)
17. for(int j = 1; j <= n; j++)
18. {
19. if(a[i-1] == b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
20. else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
21. }
22. printf("%d\n", dp[n][n]); //非严格递增,如要严格递增要对b数组去重
23. }
24. return 0;
25. }
方法二:动态规划 时间复杂度n^2,空间复杂度n
设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:
这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面
且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。
[cpp] view plain copy
1. #include<iostream>
2. #include<cstdio>
3. using namespace std;
4. const int maxn = 10005;
5. int a[maxn], dp[maxn], ans;
6. int main(void)
7. {
8. int n;
9. while(cin >> n)
10. {
11. for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1;
12. ans = 1;
13. for(int i = 1; i < n; i++)
14. for(int j = 0; j < i; j++)
15. if(a[j] < a[i] && dp[j]+1 > dp[i])
16. dp[i] = dp[j]+1, ans = max(ans, dp[i]);
17. printf("%d\n", ans);
18. }
19. return 0;
20. }
方法三:方法二+二分 时间复杂度nlongn
在第二种算法中,在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j),如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储“子序列的”最大增子序列的最末元素,即有B[f(j)] = aj在计算f(i)时,在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。
[cpp] view plain copy
1. #include<iostream>
2. #include<cstdio>
3. #include<cstring>
4. using namespace std;
5. const int maxn = 1005;
6. int a[maxn], b[maxn], dp[maxn];
7. int main(void)
8. {
9. int n;
10. while(cin >> n)
11. {
12. for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1;
13. int Len = 1, l, m, r;
14. b[1] = a[0];
15. for(int i = 1; i < n; i++)
16. {
17. l = 1, r = Len;
18. while(l <= r)
19. {
20. m = (l+r)/2;
21. if(b[m] < a[i]) l = m+1;
22. else r = m-1;
23. }
24. b[l] = a[i];
25. if(l > Len) Len++;
26. }
27. printf("%d\n", Len);
28. }
29. return 0;
30. }
上面的代码都是只求了最长的长度,但没有求路径。路径可能有多条,题目不同要求输出的不同,贴一下输出第一条路径和最后一条路径的代码。输出第一条符合的路径:
[cpp] view plain copy
1. #include<iostream>
2. #include<cstdio>
3. using namespace std;
4. const int maxn = 10005;
5. int a[maxn], dp[maxn], pre[maxn], path[maxn], ans, e;
6. int main(void)
7. {
8. int n;
9. while(cin >> n)
10. {
11. for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1, pre[i] = -1;
12. ans = 1; //初始最大长度为1,结尾在a[0]
13. e = 0;
14. for(int i = 1; i < n; i++)
15. for(int j = 0; j < i; j++)
16. if(a[j] < a[i] && dp[j]+1 > dp[i])
17. {
18. dp[i] = dp[j]+1;
19. pre[i] = j; //记录每个点i的上一个最长序列,存在已他自己为下标的pre里
20. if(dp[i] > ans) ans = dp[i], e = i; //因为是>,而不是>=所以最后一个元素肯定是所有里面最前面的
21. }
22. printf("%d\n", ans);
23. //路径要逆推回去
24. for(int i = 0, k = ans; i < ans; i++)
25. {
26. path[k--] = a[e]; //从后往前推,最后一个节点为e;把他的值a【e】输出了
27. e = pre[e]; //pre【e】存了上一个他的节点,也就是他之前最长的长度的最后一个字母
28. }
29. for(int i = 1; i <= ans; i++)
30. {
31. if(i-1) printf(" ");
32. printf("%d", path[i]);
33. }
34. printf("\n");
35. }
36. return 0;
37. }
输出最后一条路径
[cpp] view plain copy
1. #include<iostream>
2. #include<cstdio>
3. using namespace std;
4. const int maxn = 10005;
5. int a[maxn], dp[maxn], path[maxn];
6. int main(void)
7. {
8. int n, ans;
9. while(cin >> n)
10. {
11. ans = 0;
12. for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1;
13. for(int i = 1; i < n; i++)
14. for(int j = 0; j < i; j++)
15. if(a[j] < a[i] && dp[j]+1 > dp[i])
16. dp[i] = dp[j]+1, ans = max(ans, dp[i]);
17. printf("%d\n", ans);
18. for(int i = n-1, j = ans; i > -1; i--) //从后往前找,第一个找的肯定是最后面那个最长的最后一个元素
19. if(dp[i] == j) path[j--] = a[i];
20. for(int i = 1; i <= ans; i++)
21. {
22. if(i != 1) printf(" ");
23. printf("%d", path[i]);
24. }
25. printf("\n");
26. }
27. return 0;
28. }
//求原串与逆序串的最大公共子序列(滚动数组)
[cpp] view plain copy
1. #include<iostream>
2. #include<cstdio>
3. using namespace std;
4. const int maxn = 10005;
5. int a[maxn], dp[maxn], path[maxn];
6. int main(void)
7. {
8. int n, ans;
9. while(cin >> n)
10. {
11. ans = 0;
12. for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1;
13. for(int i = 1; i < n; i++)
14. for(int j = 0; j < i; j++)
15. if(a[j] < a[i] && dp[j]+1 > dp[i])
16. dp[i] = dp[j]+1, ans = max(ans, dp[i]);
17. printf("%d\n", ans);
18. for(int i = n-1, j = ans; i > -1; i--) //从后往前找,第一个找的肯定是最后面那个最长的最后一个元素
19. if(dp[i] == j) path[j--] = a[i];
20. for(int i = 1; i <= ans; i++)
21. {
22. if(i != 1) printf(" ");
23. printf("%d", path[i]);
24. }
25. printf("\n");
26. }
27. return 0;
28. }