DP-最长公共子序列LCS、最长上升子序列LIS

该博客是小学期学算法和做OJ题的经验总结,重点介绍了最长公共子序列(LCS)和最长上升子序列(LIS)。对于LCS,提到正着遍历、倒着构造及用栈回溯;对于LIS,讲解了DP(O(n²))和贪心+二分(O(nlogn))两种方法,并给出状态设计、转移方程及代码思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小学期学的算法和做OJ题的各种经验总结


最长公共子序列 LCS:

正着遍历,倒着构造
最长子序列
c[i,j]表示:(x1,x2....xi) 和 (y1,y2...yj) 的最长公共子序列的长度
序列回溯,可以用栈。

//dp初始化为0
for(i=1;i<=lena;i++){
	for(j=1;j<=lenb;j++){
		if(a[i-1]==b[j-1]){
			dp[i][j]=dp[i-1][j-1]+1;
		}else{
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	}
}
printf("%d\n",dp[lena][lenb]);

最长上升子序列 LIS

参考:https://blog.youkuaiyun.com/lxt_Lucia/article/details/81206439

DP: O(n2)

状态设计:F[i] 代表以 A[i] 结尾的 LIS 的长度

边界处理:F [ i ] = 1 (1 <= i <= n)

状态转移:遍历i之前的数中找到所有比A[i] 小的数的位置j,如果没有就是自身
F i = { max ⁡ ( F j + 1 ) , j = 1 ⋯ i − 1 且 A j &lt; A i F i ( 初 始 化 时 为 1 ) , 第 一 个 式 子 没 有 满 足 的 条 件 , 即 max ⁡ ( F j + 1 ) = 1 , 则 自 己 为 序 列 开 头 F_i = \begin{cases} \max(F_j + 1 ), &amp; j = 1\cdots i - 1且A_j &lt; A_i \\ F_i(初始化时为1),&amp; 第一个式子没有满足的条件,\\&amp;即\max(F_j+1)=1,则自己为序列开头 \end {cases} Fi=max(Fj+1),Fi1,j=1i1Aj<Aimax(Fj+1)=1
综合起来就变成了
F i = max ⁡ { F j + 1 ,   F i   ∣   w h e r e   1 ≤ j ≤ i − 1   a n d   A j &lt; A i } F_i = \max\{F_j+1,\space F_i \ | \ where\ 1\le j \le i-1 \ and \ A_j \lt A_i\} Fi=max{Fj+1, Fi  where 1ji1 and Aj<Ai}
ans即为以每个数为结尾的最长子序列中最大的

代码

for (int i = 1; i <= n; i++) f[i] = 1;
	
for (int i = 1; i <= n; i++) 
	for (int j = 1; j < i; j++)
		if (a[j] < a[i])
			f[i] = max(f[i], f[j] + 1);

for (int i = 1; i <= n; i++)
	ans = max(ans, f[i]);

贪心+二分O(nlogn)

low 数组,lowm 表示长度为m的LIS结尾元素的最小值(初始化为INF),对于每一个长度为m的LIS,一定是结尾元素越小越有潜力拼的更长。因此我们维护low数组,用len记录LIS当前的长度,对于每一个ai

  • 如果ai > lowlen 就拼到LIS里,即令low++len=ai
  • 如果ai < lowm(m是满足条件 lowk > ai 的第一个位置) ,说明ai作为长度为m的LIS的结尾元素更有潜力,就把ai 跟lowm替换。
  • 找满足条件的第一个位置可以利用二分搜索,
  • 可以直接用STL函数lower_bound(默认小于比较)
    • 用法:lower_bound(first, last, k[, cmp]) 返回first到last中不满足*it<k或cmp(*it,k)的第一个位置(地址),默认情况下即返回数组中第一个大于k的数的地址;
    • int m = lower_bound(low+1, low+1+len, a[i]) - low;地址 - 地址 = 下标

代码:

int main() {
	int low[MAXN] = INF;//长度为m的LIS结尾元素的最小值
	int a[MAXN], n;
	//初始条件
	low[1] = a[i];
	int len=1;

	for (int i = 2; i <= n; i++){
		if(a[i]>low[len]) low[++len]=a[i];//拼入LIS
		else{
			//找到第一个大于a[i]的位置,更新成a[i]
			int m = lower_bound(low+1,low+1+n,a[i]) - low;
			low[m] = a[i];
		}
	}
	cout<<len;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值