最长递减子序列

最长递减子序列

1、面试题目

求一个数组的最长递减子序列比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5, 4,3,2}

2、解题方法

采用动态规划解决题目

Ø  最长公共子序列方法解决

Ø  直接根据动态转移方程解决

2.1最长公共子序列方法

2.1.1原理

设原来数组为X[1..n],而数组Y[1…n]为数组X[]的非递增序列,现在我们求X[1…n],Y[1…n]的最长公共子序列,现在简要说下最长公共子序列的原理(详解见算法导论第2版Page208):

假设序列X={x1, x2, …, xn}和Y ={y1, y2, …,yn}的最长公共子序列为Z = {z1, z2,.., zk}

1)  如果xm = yn, 则zk = xm =yn,且zk-1是xm-1与yn-1的一个最长公共子序列

2)  如果xm!= yn,且zk != xm,那么Z是xm-1与yn的一个最长公共子序列

3)  若果xm != yn,且zk != yn, 那么Z是xm与yn-1的一个最长公共子序列

所以状态转移方程如下(本来画了图表示的,但是图没有显示,暂且这样吧):

dp[i,j] =  <1> 0                                                 if  i=0或者j =0

               <2> dp[i-1, j-1] + 1                         if i , j >0 and xi = yj

               <3>max(dp[i-1][j], dp[i][j-1])          if   i, j > 0 and xi != yj

根据上述状态方程,以序列X[1…n]和Y[1…n]为输入参数,输出参数有dp[i][j]和path[i][j]

其中dp[i][j]表示X[0…i]和Y[0…j]的最长公共子序列的长度,path[i][j]标志着dp[i][j]值是如何取得的,也就是最长公共子序列的路径,即

path[i, j] = <1>   1                      if i , j >0 and xi = yj

                   <2>  2                      if i, j >0 and xi != yj and dp[i-1, j] > dp[i, j-1] 

                   <3>  3                      if i, j >0 and xi != yj and dp[i-1, j] < dp[i, j-1] 

最长公共子序列的代码如下:

int LCS(int X[], int xlen, int Y[], int ylen)

{

int i, j;

int maxlen;

memset(dp, 0,sizeof(dp));

memset(path, 0,sizeof(path));

maxlen = 0;

for(i = 1;i <= xlen;++i)

{

     for(j = 1; j<= ylen; ++j)

     {

         if(X[i-1]== Y[j-1])

         {

             dp[i][j]= dp[i-1][j-1] +1;

             path[i][j]= 1;

         }

         else if(dp[i-1][j]  > dp[i][j-1])

         {

             dp[i][j]= dp[i-1][j];

             path[i][j]= 2;

         }

         else

         {

             dp[i][j]= dp[i][j-1];

             path[i][j]= 3;

         }

 

         if(dp[i][j]> maxlen)

         {

             maxlen= dp[i][j];

         }

     }

 

}

 

return maxlen;

}

LCS可以在O(n^2)的时间内序列X={x1,x2,..xm}和Y={y1,y2,…,yn}最长递减序列的长度,同时搜索path[][]数组可以找到最长非递增子序列, 方法如下:

1)当path[i][j]= 1,表示x[i]==y[j],那么Xi和Yj的最长公共子序列就是在Xi-1和Yj-1的基础上加上Xi或者Yj得到的

2)当path[i][j]= 2,表示x[i]==y[j],那么Xi和Yj的最长公共子序列就是Xi-1和Yj的最长公共子序列

3)当path[i][j]= 3,表示x[i]==y[j],那么Xi和Yj的最长公共子序列就是Xi和Yj-1的最长公共子序列

代码如下:

void LCS_path(int path[][NSIZ], int X[], int i, int j)

{

if(i <= 0 || j<= 0)

{

     return;

}

 

if(path[i][j] == 1)

{

     LCS_path(path,X, i-1, j-1);

     printf("%d", X[i-1]);

}

else

{

     if(path[i][j]== 2)

     {

         LCS_path(path,X, i-1, j);

     }

     else

     {

         LCS_path(path,X, i, j-1);

     }

}

}

2.1.2测试

测试代码如下:

#include 
#include 
using namespace std;

#define NSIZ 120
int dp[NSIZ][NSIZ];
int path[NSIZ][NSIZ];

int cmp(int a, int b)
{
	return a > b;
}

int LCS(int X[], int xlen, int Y[], int ylen)
{
	int i, j;
	int maxlen;
	memset(dp, 0, sizeof(dp));
	memset(path, 0, sizeof(path));
	maxlen = 0;
	for(i = 1;i <=xlen; ++i)
	{
		for(j = 1; j<= ylen; ++j)
		{
			if(X[i-1]== Y[j-1])
			{
				dp[i][j]= dp[i-1][j-1] +1;
				path[i][j]= 1;
			}
			else if(dp[i-1][j]  > dp[i][j-1])
			{
				dp[i][j]= dp[i-1][j];
				path[i][j]= 2;
			}
			else
			{
				dp[i][j]= dp[i][j-1];
				path[i][j]= 3;
			}

			if(dp[i][j]> maxlen)
			{
				maxlen= dp[i][j];
			}
		}

	}

	return maxlen;
}

void LCS_path(int path[][NSIZ], int X[], int i, int j)
{
	if(i <= 0 || j<= 0)
	{
		return;
	}

	if(path[i][j] == 1)
	{
		LCS_path(path,X, i-1, j-1);
		printf("%d", X[i-1]);
	}
	else
	{
		if(path[i][j]== 2)
		{
			LCS_path(path,X, i-1, j);
		}
		else
		{
			LCS_path(path,X, i, j-1);
		}
	}
}

int main()
{
	int X[] = {9, 4, 3,2, 5, 4, 3, 2};
	int Y[NSIZ];
	int maxlen;
	int n = sizeof(X)/sizeof(X[0]);

	memcpy(Y, X, n * sizeof(int));
	sort(Y, Y + n,cmp);
	maxlen = LCS(X, n, Y, n);
	LCS_path(path, X, n, n);

	return 0;
}


2.2直接根据动态转移方程解决

2.2.1 原理

典型的DP问题,设原数组X[1…n],另设辅助数组dp[i]表示从i到n这一段中以n结束的最长递减子序列的长度,设定初始的dp[i]=1(0 <= i X[j].同样时间复杂度为O(n^2);

函数代码:

int longest_decrease_seq(int X[], int n)

{

int i, j;

int maxlen;

 

for(i = 0;i <=n; ++i)

{

     dp[i] = 1;

}

 

maxlen = dp[0];

 

for(i = n-1;i >=0; --i)

{

     for(j = i + 1;j < n; ++j)

     {

         if(X[i]> X[j])

         {

             dp[i] =max(dp[i], dp[j] + 1);

             if(maxlen< dp[i]) //更新最长的序列的长度。

             {

                 maxlen= dp[i];

             }

         }

     }

}

//输出最大递减子序列

for(j = 0; j

{

     if(dp[j] ==maxlen)

     {

         printf("%d", X[j]);

         maxlen--;

     }

 

}

 

return maxlen;

}

2.2.2测试

#include 
using namespace std;

#define NSIZ 1200

int dp[NSIZ]; //d[i]表示从num[i]到num[n]之间的最长子序列的长度。

int longest_decrease_seq(int X[], int n)
{
	int i, j;
	int maxlen;

	for(i = 0;i <=n; ++i)
	{
		dp[i] = 1;
	}

	maxlen = dp[0];

	for(i = n-1;i >=0; --i)
	{
		for(j = i + 1;j < n; ++j)
		{
			if(X[i]> X[j])
			{
				dp[i] =max(dp[i], dp[j] + 1);
				if(maxlen< dp[i]) //更新最长的序列的长度。
				{
					maxlen= dp[i];
				}
			}
		}
	}
	//输出最大递减子序列
	for(j = 0; j 


2.3 对方法2的改进算法

2.3.1 原理

这里参考了http://qiemengdao.iteye.com/blog/1660229的博文;

在方法2中,在X[i+1…n]中寻找满足条件X[i]> X[j]时最大的dp[j]时,采用的顺序查找算法时间复杂度为O(n), 现在我们用一个数组C[1…k]保存这从第1个元素到第i个元素的最长递减序列,这样数组C[]的长度就是最后的最长递减子序列的长度。

举个例子说明:

X[] = {9,4,3,2,5,4,3,2},

1)  对于X[1]放到C[1]中,C[1]是有序的, 即C[1] = 9,只有一个元素时最长递减序列为9,clen=1;

2)对于X[2],由于X[2] < C[1]那么直接把X[2]放到C[clen++]处,此时clen=2;

同理对X[3], x[4],此时C[] ={9,4, 3, 2},那么clen=4;

3)对于X[5] =5, X[5]>C[clen-1]=2, 那么在已经排序的C中查找,则X[5]应该在C[2]位置,那么C[2]直接被替换为X[5],现在C[]={9, 5, 3, 2}, clen=4;

4)对于X[6] =4, X[5]>C[clen-1]=2, 那么在已经排序的C中查找,则X[5]应该在C[3]位置,那么C[3]直接被替换为X[6],现在C[]={9, 5, 4, 2}, clen=4;

5)对于X[7] =3, X[5]>C[clen-1]=2, 那么在已经排序的C中查找,则X[5]应该在C[4]位置,那么C[4]直接被替换为X[7],现在C[]={9, 5, 4, 3}, clen=4;

6)对于X[8] =2, X[5]

至此,整个过程明白了吧。

在C[]中查找的过程,是替换原来的值而不用移动,所以利用二分查找过程,使得整个算法的时间复杂度为O(nlgn);

关键代买如下:

//注意C[]为递减序列,左边大,右边小

int binary_search(int c[], int n, int key)

{

   int left,right, mid;

   left = 0,right = n-1;

   while(left<= right)

   {

       mid =left + (right -left) /2 ;

       if(key== c[mid])

       {

           returnmid;

       }

       elseif (key < c[mid]) //当c[mid]大于key时,应该往右边找

       {

           left= mid + 1;

       }

       else当c[mid]小于key时,应该往左边找

       {

           right= mid - 1;

       }

   }

   returnleft;

}

 

int longest_decrease_seq(int X[], int n)

{

   int i, j;

   int clen;

 

   intC[NSIZ];

 

   C[0] =X[0];

   clen = 1;

   for(i =1;i < n; ++i)

   {

       if(X[i]< C[clen-1])

       {

           C[clen++]= X[i];

       }

       else

       {

           j=  binary_search(C, clen, X[i]);

           C[j]= X[i];

       }

   }

 

   //输出最大递减子序列

   for(i =0; i < clen; ++i)

   {

 

       printf("%d", C[i]);

   }

 

   returnclen;

}

2.3.2测试

#include 
#include 
#include 
using namespace std;
 
#define NSIZ 1200
 
int C[NSIZ]; //保存着从X[1]到X[i]最长公共序列
 
//注意C[]为递减序列,左边大,右边小
int binary_search(int c[], int n, int key)
{
         int left, right, mid;
         left= 0, right = n-1;
         while(left<= right)
         {
                   mid= left + (right -left) /2 ;
                   if(key== c[mid])
                   {
                            return mid;
                   }
                   else if (key < c[mid]) //当c[mid]大于key时,应该往右边找
                   {
                            left = mid + 1;
                   }
                   else当c[mid]小于key时,应该往左边找
                   {
                            right = mid - 1;
                   }
         }
         return left;
}
 
 
int longest_decrease_seq(int X[], int n)
{
         int i, j;
         int clen;
 
         int C[NSIZ];
 
         C[0]= X[0];
         clen= 1;
         for(i= 1;i < n; ++i)
         {
                   if(X[i]< C[clen-1])
                   {
                            C[clen++]= X[i];
                   }
                   else
                   {
                            j=  binary_search(C, clen, X[i]);
                            C[j]= X[i];
                   }
         }
 
         //输出最大递减子序列
         for(i= 0; i < clen; ++i)
         {
 
                   printf("%d", C[i]);
         }
 
         return clen;
}
 
int main()
{
         int X[] = {9, 4, 3, 2, 5, 4, 3, 2};
         int n = sizeof(X)/sizeof(X[0]);
         longest_decrease_seq(X,n);
         return 0;
}
 
 


你可能感兴趣的:(面试题目)