查了资料发现是一道经典的题目
一、资料
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。
四、总结:掌握了基本的动态规划思想。