此前在动态规划一讲:动态规划(3)-最长递增子序列 曾说过此问题,当前是的双重循环是O(n^2)的复杂度。
后来在网上看到说LIS问题有O(nlogn)的算法,于是拿来小研究了一下。
这个算法其实已经不是DP了,有点像贪心。至于复杂度降低其实是因为这个算法里面用到了二分搜索。本来有N个数要处理是O(n),每次计算要查找N次还是O(n),一共就是O(n^2);现在搜索换成了O(logn)的二分搜索,总的复杂度就变为O(nlogn)了。
这个算法的具体操作如下(by RyanWang):
开一个栈,每次取栈顶元素top和读到的元素temp做比较,如果temp > top 则将temp入栈;如果temp < top则二分查找栈中的比temp大的第1个数,并用temp替换它。 最长序列长度即为栈的大小top。
这也是很好理解的,对于x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替换Stack[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。
用该算法完成POJ2533的具体代码如下:
#include <iostream>
#define SIZE 1001
using namespace std;
int main()
{
int i, j, n, top, temp;
int stack[SIZE];
cin >> n;
top = 0;
/* 第一个元素可能为0 */
stack[0] = -1;
for (i = 0; i < n; i++)
{
cin >> temp;
/* 比栈顶元素大数就入栈 */
if (temp > stack[top])
{
stack[++top] = temp;
}
else
{
int low = 1, high = top;
int mid;
/* 二分检索栈中比temp大的第一个数 */
while(low <= high)
{
mid = (low + high) / 2;
if (temp > stack[mid])
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
/* 用temp替换 */
stack[low] = temp;
}
}
/* 最长序列数就是栈的大小 */
cout << top << endl;
//system("pause");
return 0;
}
这其中用到了二分查找第一个大于等于的,其实C++里面的有一个函数可用代替二分。
lower_bound 函数
下面是使用lower_bound优化最长上升子序列。由于长度相同的上升子序列只需要保存结尾最小的那个,而长度递增时,结尾数字的大小也是递增的。最长上升子序列就是找出比他大的第一个数。前面的数都比他小,所以他和这个数的长度相同。然后由于他比较然后小,更新找到的那个值。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int num[10]={3,6,3,2,4,6,7,5,4,3};
const int INF=0x3f3f3f3f;
int l=10;
int g[100];
int d[100];
int main()
{
fill(g,g+l,INF);
int max_=-1;
for(int i=0;i<l;i++)
{
int j=lower_bound(g,g+l,num[i])-g;
d[i]=j+1;
if(max_<d[i])
max_=d[i];
g[j]=num[i];
}
printf("%d\n",max_);
return 0;
}
此前在动态规划一讲:动态规划(3)-最长递增子序列 曾说过此问题,当前是的双重循环是O(n^2)的复杂度。
后来在网上看到说LIS问题有O(nlogn)的算法,于是拿来小研究了一下。
这个算法其实已经不是DP了,有点像贪心。至于复杂度降低其实是因为这个算法里面用到了二分搜索。本来有N个数要处理是O(n),每次计算要查找N次还是O(n),一共就是O(n^2);现在搜索换成了O(logn)的二分搜索,总的复杂度就变为O(nlogn)了。
这个算法的具体操作如下(by RyanWang):
开一个栈,每次取栈顶元素top和读到的元素temp做比较,如果temp > top 则将temp入栈;如果temp < top则二分查找栈中的比temp大的第1个数,并用temp替换它。 最长序列长度即为栈的大小top。
这也是很好理解的,对于x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替换Stack[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。
用该算法完成POJ2533的具体代码如下:
#include <iostream>
#define SIZE 1001
using namespace std;
int main()
{
int i, j, n, top, temp;
int stack[SIZE];
cin >> n;
top = 0;
/* 第一个元素可能为0 */
stack[0] = -1;
for (i = 0; i < n; i++)
{
cin >> temp;
/* 比栈顶元素大数就入栈 */
if (temp > stack[top])
{
stack[++top] = temp;
}
else
{
int low = 1, high = top;
int mid;
/* 二分检索栈中比temp大的第一个数 */
while(low <= high)
{
mid = (low + high) / 2;
if (temp > stack[mid])
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
/* 用temp替换 */
stack[low] = temp;
}
}
/* 最长序列数就是栈的大小 */
cout << top << endl;
//system("pause");
return 0;
}