算法 LC 动态规划 - 最大递增子序列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法 LC 动态规划 - 最大递增子序列相关的知识,希望对你有一定的参考价值。

参考技术A

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

思路1:动态规划

定义dp[i]为以i元素结尾的最长递增子序列的长度

我们从小到大计算dp数组的值,在计算dp[i]之前,我们已经计算出dp[0…i−1] 的值,则状态转移方程为
dp[i] = max(dp[j]) + 1 (0<=j<i 且 num[i]>nums[j])
边界条件:dp[0] = 1

思路2:贪心算法+二分法查找

想获得最长递增子序列,我们需要让递增子序列上升得尽可能的慢,也就是说每次在递增子序列最后加上的那个数尽可能的小

定义dp[len]为长度为len的递增子序列末尾最小元素,边界条件:dp[1] = nums[0]

d[len]是关于len单调递增的

对于任意长度为i的递增子序列(末尾元素为x),我们在末尾删除一个元素,都可以得到一个长度为i-1的递增子序列(末尾元素为y),很明显y<x。
dp[i] 表示长度为i的递增子序列的最小元素,即dp[i] = min(x0,x1,x2...),dp[i-1] 表示长度为i-1的递增子序列的最小元素,即dp[j] = min(y0,y1,y2...),又由于x0>y0,x1>y1,...,则dp[i]>dp[i-1]
因而d[len]是关于len单调递增的。

我们从小遍历数组nums,比较nums[i]和dp[len],更新len和dp[len]
如果nums[i] > dp[len],表示当前长度len的递增子序列的末尾元素小于nums[i],则len=len+1,dp[len] = nums[i];
如果nums[i] < dp[len],则在dp数组中查找,找到一个k(0<k<=len),dp[k-1]<nums[i]<dp[k],更新dp[k] = nums[i],如果找不到k,则说明所有dp j 都比nums[i],则更新dp[0] = nums[i]

在nums[i] < dp[len],查找k的过程中,由于dp是单调递增的,我们可以通过二分法查找,即查找第一个比nums[i]小的数d[k],更新d[k+1]=nums[i]

参考: https://leetcode-cn.com/leetbook/read/top-interview-questions-medium/xwhvq3/
https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode-soluti/

算法之动态规划(最长递增子序列——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个数字结束的递增子序列最长长度。其次,这些子问题之间存在某种联系,以任意一个数字结束的递增子序列长度,与以排在该数字之前所有比它小的元素结尾的最长递增子序列长度有关,且仅与其数字量有关,而与其具体排列无关。



 

 

 

 

以上是关于算法 LC 动态规划 - 最大递增子序列的主要内容,如果未能解决你的问题,请参考以下文章

关于用动态规划法求最大公共子序列的问题

动态规划 - 单调递增最长子序列

五大常用算法:分治动态规划贪心回溯和分支界定

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

算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)

算法 ---- 子序列系列问题题解(子序列编辑距离回文系列问题)