最长下降子序列

本文介绍了如何求解最长下降子序列问题,提供了两种解法:DP+暴力搜索以及贪心+二分搜索。通过动态规划,从第二个元素开始遍历,寻找大于它的元素以更新最长下降子序列的长度。贪心算法中,利用二分查找在保持子序列长度不变的情况下扩大下降子序列的最小值。每种解法都包含了详细的思路和代码实现。


题目

给出一组数据 nums,求出其最长下降子序列(子序列允许不连续)的长度。(类似于lc的最长递增子序列

示例:

输入:

6 // 数组元素个数
3 4 3 5 2 1 // 数组

输出:

4 // 最长下降子序列为4321,长度为4

解法

DP+暴搜

思路

DP数组 表示 nums数组 对应下标的元素作为末尾时最长下降子序列的长度,因此将 DP数组中的元素 全部初始化为 1(最起码这个下降子序列里有当前元素)。

nums第二个元素开始(记为 i),向前搜索所有大于它的元素(记为 j),找到后 dp[i] = max(dp[i], dp[j] + 1) 。举个例子会更直观:

nums = 3 4 3 5 2 1
dp =   1 1 2 1 3 4
           ⬆
   形成下降子序列:4,3   

i=4, nums[i]=2 时,这个状态转移方程显得尤为重要:

  • j=0 开始遍历,第一个大于 2 的元素是 j=1, nums[j]=4dp[i]=dp[j]+1=2,意味着可以形成下降子序列:4,2,长度为 dp[i]=2
  • 第二个大于 2 的元素是 j=2, nums[j]=3dp[i]=dp[j]+1=3,意味着可以形成下降子序列:4,3,2
  • 第三个大于 2 的元素是 j=3, nums[j]=5dp[i]=dp[i]=3,意味着形成 5,2 不如形成 4,3,2 更长,所以仍保持原来的下降子序列。

值得注意的是最长下降子序列的长度是 DP数组 中最大的元素而非尾元素,举个例子:

nums = 3 4 3 5 2 3
dp =   1 1 2 1 3 2

因此在构建 DP数组 的同时要记得保存最大元素哦~

代码实现

int main() {
	int n, res = 1;
	cin >> n;
	vector<int> v(n), dp(n, 1);
	for (int i = 0; i < n; i++) {
		cin >> v[i];
	}
	for (int i = 1; i < n; i++) {
		for (int j = 0; j < i; j++) {
			if (v[j] > v[i]) dp[i] = max(dp[j] + 1, dp[i]);
		}
		res = max(dp[i], res);
	}
	cout << res << endl;
}

在这里插入图片描述


贪心+二分

思路

dp数组: 用来暂存一个 下降子序列,注意,这里的序列并非真正的最长下降子序列,至于原因下文解释。由于题目要求的是最长下降子序列的长度,并不用返回序列的具体排列,因此dp数组的长度就是本题的答案

该方法思路分三步:请客、斩首、收下当狗。

  1. 请客:遍历 数据数组 中每个元素,和 dp数组 的尾元素比较。
  2. 收下当狗:如果比较结果是当前遍历到的元素小于 dp数组 尾元素,则当前元素直接加入 dp数组,成为新的尾元素
  3. 斩首:斩 dp数组旧元素的首,当前遍历到的元素作为新元素,自然要先收下当狗,具体做法是:
    • 通过二分法找到 dp数组 中所有小于当前元素中最大的那个,用当前元素替换掉它。举个例子,比如:dp数组元素是 9,5,3,1。当前遍历到的元素是 6,那么dp数组会变成:9,6,3,1

为什么说 dp数组 暂存的下降子序列只是和真正的最长下降子序列长度相等,两者内容并不一样?

原因就在于第3点——斩首,斩首的目的是 在不改变子序列长度的基础上扩大下降子序列的最小值,用具体例子来说明:

假如给出的数据是 3,4,3,5,2,1。那么 dp数组 的变化会是:

dp数组内容当前遍历到的元素最长下降子序列 |
333
444 或 3
4,334,3
5,354,3
5,3,224,3,2
5,3,2,114,3,2,1

代码实现

int main() {
	int n;
	cin >> n;
	vector<int> v(n), dp;
	for (int i = 0; i < n; i++) {
		cin >> v[i];
	}
	dp.push_back(v[0]);
	for (int i = 1; i < n; i++) {
		if (v[i] < dp.back()) dp.push_back(v[i]);
		else {
			int l = 0, r = dp.size()-1;
			while (l < r) {
				int m = l + (r - l) / 2;
				if (v[i] < dp[m])l = m + 1;
				else r = m;
			}
			dp[l] = v[i];
		}
		for (int j : dp) {
			cout << j << " ";
		}cout << endl;
	}
	cout << dp.size() << endl;
}

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

·Jormungand

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值