算法之动态规划(最长递增子序列——LIS)

时间:2023-03-09 19:12:26
算法之动态规划(最长递增子序列——LIS)

最长递增子序列是动态规划中最经典的问题之一,我们从讨论这个问题开始,循序渐进的了解动态规划的相关知识要点。

在一个已知的序列 {a1, a 2,...an}中,取出若干数组成新的序列{ai1, ai 2,...aim},其中下标 i1、i2…im保持递增,即新数列中的各个数之间依旧保持原数列中的先后顺序,那么我们称新的序列{ai1, ai 2,...aim}为原序列的一个子序列。若在子序列中,当下标 ix > iy时,aix > aiy,那么我们称这个子序列为原序列的一个递增子序列。最长递增子序列问题,就是在一个给定的原序列中,求得其最长递增子序列长度。

有序列 {a1, a 2,...an},我们求其最长递增子序列长度。按照递推求解的思想,我们用 F[i]代表若递增子序列以 ai结束时它的最长长度。当i较小,我们容易直接得出其值,如 F[1] = 1。那么,如何由已经求得的 F[i]值推得后面的值呢?假设,F[1]到 F[x-1]的值都已经确定,注意到,以ax结尾的递增子序列,除了长度为1的情况,其它情况中,ax都是紧跟在一个由 ai(i<x)组成递增子序列之后。要求以ax结尾的最长递增子序列长度,我们依次比较ax与其之前所有的 ai(i<x),若ai小于ax,则说明ax可以跟在以ai结尾的递增子序列之后,形成一个新的递增子序列。又因为以ai结尾的递增子序列最长长度已经求得,那么在这种情况下,由以ai结尾的最长递增子序列再加上ax得到的新的序列,其长度也可以确定,取所有这些长度的最大值,我们即能得到 F[x]的值。特殊的,当没有ai(i<x)小于 ax,那么以ax结尾的递增子序列最长长度为1。

F[ x] = max{1, F[i] + 1 | ai < ax & &i < x};

我们给出求序列{1,4,3,2,6,5}的最长递增子序列长度的所有 F[i]供读者参考。

F[1](1)

F[2](4)

F[3](3)

F[4](2)

F[5](6)

F[6](5)

1

2

2

2

3

3

总结一下,求最长递增子序列的递推公式为:

F[1] = 1;

F[i] = max{1, F[ j] + 1 | aj < ai & & j < i};

接下来是一个应用的列子,我们通过这个列子,来再度深入的了解下LIS。

某国为了防御敌国的导弹袭击,发展中一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于等于前一发的高度。某天,雷达捕捉到敌国导弹来袭。由于该系统还在试用阶段,所以只用一套系统,因此有可能不能拦截所有的导弹。

输入:
第一行输入测试数据组数N(1<=N<=10)
接下来一行输入这组测试数据共有多少个导弹m(1<=m<=20)
接下来行输入导弹依次飞来的高度,所有高度值均是大于0的正整数。
输出:
输出最多能拦截的导弹数目
样例输入:
2
8
389 207 155 300 299 170 158 65
3
88 34 65
样例输出:
6
2
#include <stdio.h>
int max(int a,int b) {return a > b ? a : b;} //取最大值函数
int list[26]; //按袭击事件顺序保存各导弹高度
int dp[26]; //dp[i]保存以第i个导弹结尾的最长不增子序列长度
int main() {
int n;
while (scanf("%d",&n) != EOF) {
for (int i = 1;i <= n;i ++) {
scanf("%d",&list[i]);
} //输入
for (int i = 1;i <= n;i ++) { //按照袭击时间顺序确定每一个dp[i]
int tmax = 1; //最大值的初始值为1,即以其结尾的最长不增子序列长度至少为1
for (int j = 1;j < i;j ++) { //遍历其前所有导弹高度
if (list[j] >= list[i]) { //若j号导弹不比当前导弹低
tmax = max(tmax,dp[j] + 1); //将当前导弹排列在以j号导弹结尾的最长不增子序列之后,计算其长度dp[j] + 1,若大于当前最大值,则更新最大值
}
}
dp[i] = tmax; //将dp[i]保存为最大值
}
int ans = 1;
for (int i = 1;i <= n;i ++) {
ans = max(ans,dp[i]);
} //找到以每一个元素结尾的最长不增子序列中的最大值,该最大值即为答案
printf("%d\n",ans); //输出
}
return 0;
}  

最长递增子序列问题,是我们接触的第一个真正意义上的动态规划问题。我们来回顾它的特点。首先,我们将这个问题分割成许多子问题,每个子问题为确定以第 i个数字结束的递增子序列最长长度。其次,这些子问题之间存在某种联系,以任意一个数字结束的递增子序列长度,与以排在该数字之前所有比它小的元素结尾的最长递增子序列长度有关,且仅与其数字量有关,而与其具体排列无关。