LIS

方法一:DP

dp动态规划

状态设计:dp[i]代表以a[i]结尾的LIS的长度
状态转移:dp[i]=max(dp[i], dp[j]+1) (0<=j< i, a[j]< a[i])
时间复杂度:O(N^2)

例题:https://blog.csdn.net/y201619819/article/details/78354348

方法二:贪心+二分

时间复杂度Nlog(N)

a[i]表示第i个数据。
dp[i]表示表示长度为i+1的LIS结尾元素的最小值。
利用贪心的思想,对于一个上升子序列,显然当前最后一个元素越小,越有利于添加新的元素,这样LIS长度自然更长。
因此,我们只需要维护dp数组,其表示的就是长度为i+1的LIS结尾元素的最小值,保证每一位都是最小值,这样子dp数组的长度就是LIS的长度。

dp数组具体维护过程同样举例讲解更为清晰。
同样对于序列 a(1, 7, 3, 5, 9, 4, 8),dp的变化过程如下:

dp[0] = a[0] = 1,长度为1的LIS结尾元素的最小值自然没得挑,就是第一个数。 (dp = {1})
对于a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
对于a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替换dp[1],令dp[1]=a[2],因为长度为2的LIS,结尾元素自然是3好过于7,因为越小这样有利于后续添加新元素。 (dp = {1, 3})
对于a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
对于a[4]=9,a[4]>dp[2],因此同样直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
对于a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替换值为5的dp[2],因此长度为3的LIS,结尾元素为4会比5好,越小越好嘛。(dp = {1, 3, 4, 9})
对于a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替换值为9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})
ok,这样子dp数组就维护完毕,所求LIS长度就是dp数组长度4。
通过上述求解,可以发现dp数组是单调递增的,因此对于每一个a[i],先判断是否可以直接插入到dp数组尾部,即比较其与dp数组的最大值即最后一位;如果不可以,则找出dp中第一个大于等于a[i]的位置,用a[i]替换之。
这个过程可以利用二分查找,因此查找时间复杂度为O(logN),所以总的时间复杂度为O(NlogN)

例题:

LIS 是最长上升子序列。什么是最长上升子序列? 就是给你一个序列,请你在其中求出一段最长严格上升的部分,它不一定要连续。

就像这样:2, 3, 4, 7 和 2, 3, 4, 6 就是序列 2 5 3 4 1 7 6 的两个上升子序列,最长的长度是 4。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 9;
int f[N], a[N];
int n;
int find(int l, int r, int x) {//二分查找要插入的位置
	while (l < r) {
		int mid = (l + r) / 2;
		if (f[mid] < x) {
			l = mid + 1;
		}
		else {
			r = mid;
		}
	}
	return l;
}
int lis() {
	int len = 0;
	for (int i = 0; i < n; i++) {
		int k = find(0, len, a[i]);
		f[k] = a[i];
		if (k == len) {
			len++;
		}
	}
	return len;
}
int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", a + i);
	}
	printf("%d\n", lis());
	return 0;
}

LCS

一道很有意思的将 lcs(最长公共子序列) 转换为了 lis(最长增长子序列) 的问题。

题目链接 :1713. 得到子序列的最少操作次数

题解链接:https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence/solution/de-dao-zi-xu-lie-de-zui-shao-cao-zuo-ci-hefgl/