刚刚开始学习动态规划,就从最长上升子序列(Longest Increasing Subsequence)开始吧。最长上升子序列是非常经典的动态规划的题目,也是很基础的题目。对于这题的有两种算法,时间复杂度分别为O(n*logn)和O(n^2) 。
动态规划求解思路分析:(O(n^2))
经典的O(n^2)的动态规划算法,设A[i]表示序列中的第i个数,F[i]表示从1到i这一段中以i结尾的最长上升子序列的长度,初始时设F[i] = 0(i = 1, 2, ..., len(A))。则有动态规划方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 10005
int main()
{
int n, b[N], len, max;
char str[N];
scanf("%d", &n);
while (n--)
{
scanf("%s", str);
len = strlen(str);
b[0] = max = 1;
for (int i=1; i<len; i++)
{
int temp = 0;
for (int j=0; j<i; j++)
if (str[i]>str[j] && b[j]>=temp)
temp = b[j];
b[i] = temp+1;
if (b[i]>max)
max = b[i];
}
printf("%d\n", max);
}
return 0;
}
贪心+二分查找:(O(nlogn))
开辟一个栈,每次取栈顶元素s和读到的元素a做比较,如果a>s, 则加入栈;如果a<s,则二分查找栈中的比a大的第1个数,并替换。 最后序列长度为栈的长度。
这也是很好理解的,对x和y,如果x<y且E[y]<E[x],用E[x]替换 E[y],此时的最长序列长度没有改变但序列Q的''潜力''增大。
举例:原序列为1,5,8,3,6,7
栈为1,5,8,此时读到3,则用3替换5,得到栈中元素为1,3,8, 再读6,用6替换8,得到1,3,6,再读7,得到最终栈为1,3,6,7 ,最长递增子序列为长度4。
这里没有开辟栈,而是开了数组b[],全部赋为最大值。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define N 10005
int binary(int a[], int len, int ch)//返回x,a[x]>=ch>a[x-1]
{
int left = 1, right = len;
int mid = (left+right)>>1;
while (left<=right)
{
if (a[mid]>ch)
right = mid-1;
else if (a[mid]<ch)
left = mid+1;
else return mid;
mid = (left+right)>>1;
}
return left;
}
int main()
{
int n, b[N], len, max;
char str[N];
scanf("%d", &n);
while (n--)
{
scanf("%s", str);
len = strlen(str);
for (int i=0; i<N; i++)
b[i] = N;
b[0] = -1, b[1] = str[0];
max = 1;
for (int i=1; i<len; i++)
{
int t = binary(b, N, str[i]);
b[t] = str[i];
if (t>max)
max = t;
}
printf("%d\n", max);
}
return 0;
}
对于如果要输出一个最长上升子序列的话,就要用到时间复杂度为O(n^2)的算法,就要多开辟一个数组pre[],用来存在每一个元素的前驱元素的数组下标。
核心代码如下:
for (int i=1; i<len; i++)
{
int temp = 0;
for (int j=0; j<i; j++)
if (str[i]>str[j] && b[j]>temp)
{
temp = b[j];
pre[i] = j;//存放前驱元素所在下标
}
b[i] = temp+1;
}