JNU OJ 1018 LIS最长递增子数列 动态规划

 

LIS之动态规划解法(这是个啥呀)

 

查了资料发现是一道经典的题目

 

一、资料

 

1.知乎上一个关于动态规划很严谨的回答:点击打开链接

 

2.百度百科,动态规划:点击打开链接

 

 

3.csdn博客 C语言代码:点击打开链接

 

 

二、初始思路(刚刚看题时,只是做一下思路的备忘没准以后发明个吊炸天的算法呢,没有写代码,可以跳过)

 

 

1.

解法一:寻找上升序列,比较它们的长度,选取最长的作为最终结果。

 

 

解法二:依次查看长度从长到短的序列,检查他们是否为上升序列。从长序列开始的好处在于,发现第一个上升序列,他一定是所有序列中最长的,运算也就结束了。

 

 

2.选择了法一,那么。查找递增序列

 

 

对于   1 5 6 9 3 4 1 2 7 8 9 11

 

 

 

首先从1开始

1.依次查看后元素的后继是否大于上升序列的最后一个元素。直到主序列结束。此时得到的序列包含了它里面所有的长度不大于它的递增子序列

 

1 5 6 9 3 4 1 2 7 8 9 11

 

y y y y n n n n n n n y

 

 

 

得到 1 5 6 9 11

 

 

2.再从第一个被跳过的元素开始,首先检查如果将它插入到前面的递增序列中,在哪个元素之后。这里对于3是1。

 

那么从1开始,跳过3之前的,使这一次的序列第二个元素为3,后面的步骤重复1。

 

1 5 6 9 3 4 1 2 7 8 9 11

 

            y y n n y y y y

 

得到1 3 4 7 8 9 11

 

 

 

3.对于整个序列重复步骤2

 

 

得到 1 2 7 8 9 11

 

 

 

此时发现忽略了序列 1 5 6 7 8 9 11

说明这个算法还不够严谨,然后懒得想了就不自己想了而去学习动态规划

 

 

 

 

三、动态规划

 

动态规划是一种思想,把一个问题求解转化为对状态的求解。

 

 

定义与详解参看知乎回答。已经写得很棒了。

动态规划

 

 

这里上根据动态规划思想自己写的代码。

 

 

 

 

 

 

 

题目:

给定一个数列,长度为N,
求这个数列的最长上升(递增)子数列(LIS)的长度.

1 7 2 8 3 4
为例。
这个数列的最长递增子数列是 1 2 3 4,长度为4;
次长的长度为3, 包括 1 7 8; 1 2 3 等.

 

 

 

 

这里选取回答中的一关键内容

//

重新定义这个问题:

给定一个数列,长度为N,
设 为:以数列中第k项结尾的最长递增子序列的长度.
求 中的最大值.

 

 

这里面Fk就是状态。

 

 

 

 

而对于来讲,都是的子问题:因为以第k项结尾的最长递增子序列(下称LIS),包含着以第中某项结尾的LIS。

 

 

 

上述状态定义好之后,状态和状态之间的关系式,就叫做状态转移方程。

比如,对于LIS问题,我们的第一种定义:

设 为:以数列中第k项结尾的最长递增子序列的长度.

设A为题中数列,状态转移方程为:

(根据状态定义导出边界情况)

用文字解释一下是:
以第k项结尾的LIS的长度是:保证第i项比第k项小的情况下,以第i项结尾的LIS长度加一的最大值,取遍i的所有值(i小于k)。

 

//

 

 

 

//lis最长上升子序列 动态规划
#include
#include

int LIS(int n, int *(&len), int *(&arr))
{
	int i;
	int max = -1;

	for (i = 1; i < n + 1; i++)  /*在保证第n项比第i项大的前提下(arr[n]>arr[i]),第n项结尾的lis长度是所有满足条件的第i项结尾的lis长度中的最大值(max(len[i]),i=1,2,...,n-1)*/
	{
		if (arr[i] < arr[n])
		{
			if (len[i] > max)
				max = len[i];
		}
	}

	if (max == -1)
	{
		len[n] = 1;
		return 0;
	}
	else
	{
		len[n] = max + 1;
		return 1;
	}


}

int main(void)
{
	int ans;
	int *arr;
	int *len;
	int i;
	int n;
	int max = 0;

	scanf("%d", &n);

	
	len = (int*)malloc((n + 1) * sizeof(int));
	len[0] = 0;
	len[1] = 1;

	arr = (int*)malloc((n + 1) * sizeof(int));
	arr[0] = 0;

	for (i = 1; i < n + 1; i++)
	{
		scanf("%d", &arr[i]);
	}


	for (i = 2; i < n + 1; i++)
	{
		LIS(i, len, arr);
	}

	for (i = 1; i < n + 1; i++)
	{
		if (len[i] > max)
			max = len[i];
	}

	printf("%d", max);

	system("pause");

	return 0;
}

 

 

 

1.先看主函数

主函数里面定义了2个数组arr与len。

定义变量n存储元素个数。

 

 

arr用于存储输入的序列元素,len用于存储以i下标为结尾的最长递增子序列的长度。为方便对应下标与序号,两个数组的下标0位置置0。

 

第一个循环接收输入的数据。

 

第二个循环执行n-1次,每一次循环调用一次LIS函数。

 

第三个循环执行前,数组len已经填充完毕,第三个循环只需要得到数组中最大的值即可。

 

 

 

 

 

2.接下来是LIS函数

 

 

LIS函数的目的是处理主函数中下标为i 的len数组的数据域。

因为整个程序最终的结果是len数组中元素的最大值。

 

而每次要求一个i位置上的数组元素,就需要前面1,2,...,i-1位置上的数组元素。

 

首先注意,为了使得LIS执行后可以处理主函数中的len数组,从主函数向LIS函数传参使用了c++的引用传参。

 

 

 

根据已经分析的题目要求,第一个循环求在保证第n项比第i项大的前提下(arr[n]>arr[i]),第n项结尾的lis长度是所有满足条件的第i项结尾的lis长度中的最大值(max(len[i]),i=1,2,...,n-1)

 

 

这里max是一个判断变量,初值为-1。

if在循环中没有得到赋值,仍为初值-1,说明数据数组中i位置前面的元素都不小于它。那么以它为结尾的LIS长度即为1,len数组的元素值赋为1。

else在循环中得到了赋值,那么根据前面的分析,要求的位置上的数组元素即为max+1。

 

 

四、总结:掌握了基本的动态规划思想。

 

 

 

 

 

你可能感兴趣的:(JNU OJ 1018 LIS最长递增子数列 动态规划)