求最长上升子序列(c++ LIS 算法)

LIS 算法:计算最长不下降子序列

常见的LIS 算法有两种解法一种是类动态规划,另一种则是二分法维系一个单调队列。

假设给定数组 d, 求 d 的最长不下降子序列 

vector<int> d = {5,6,7,1,2,8}; // 总长度为 6
法一:类动态规划
        最优子结构:dp[i] 即为 d[i] 的最长子序列
        初始化: dp[i] = 1;  // 每个序列至少有其本身数字
        转移方程:d[i] > d[j] 则 dp[i] = max(dp[i],dp[j] + 1); // j 为 i + 1 ... d.size() - 1 的遍历值; 

        一:先将dp数组中每个值初始为1

vector<int> dp(d.size(),1); // 按照d的大小初始化并全部赋值为一

        二:从末尾倒数第二个元素反向遍历数组 d (因为最后一个元素的最长序列只可能为1)

for (int i = d.size() - 2; i >= 0; i--) {
    
}

        三:j 从 i + 1 遍历到 d.size() - 1, 状态转移

for (int j = i + 1; j < d.size(); j++) {
    if (d[i] < d[j]) dp[i] = max(dp[i],dp[j] + 1);
}

        四:遍历每一个dp[i] 获取最长不下降子序列长度

int maxn = 1;
for (int i = d.size() - 2; i >= 0; i--) maxn = max(maxn,dp[i]);

总代码:

int LIS(vector<int> d) {
	int maxn = 1;
	vector<int> dp(d.size(),1);
	for (int i = d.size() - 2; i >= 0; i--) {
		for (int j = i + 1; j < d.size(); j++) {
			if (d[i] < d[j]) dp[i] = max(dp[i],dp[j] + 1);
		}
		maxn = max(dp[i],maxn);
	}
	return maxn;
}
法二:二分法维系单调队列
引言:根据贪心思路,为让一个单调队列越长,在一个单调队列ans中ans[i]肯定是越小越好,这样这个单调队列后面能插入元素的机会更大

从引言推出,对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长

首先我们构造一个单调递增队列 ans = {d[0]}; 

vector<int> ans;
ans.push_back(d[0]);

注意:ans[i] 为 长度为 i 的 LIS 元素结尾最小值,从引言推出 ans.size() 即为 LIS 的值 

然后 i 从第二个元素开始遍历整个数组

for (int i = 1; i < d.size(); i++) {

}

        如果 d[i] > ans[ans.size() - 1] // 如果比队列中最大的元素还大,ans 就添加这个元素 ans.push_back(d[i]);

if (ans[ans.size() - 1] < d[i]) ans.push_back(d[i]);

        反之,就在单调队列中的找到第一个比 d[i] 大的元素并替换他,实现 ans[i] 为 长度为 i 的 LIS 元素结尾最小值

else {
    // lower_bound() 二分找到ans数组中大于等于d[i]的第一个位置,注意是大于等于
	int pos = lower_bound(ans.begin(),ans.end(),d[i]) - ans.begin(); 
	ans[pos] = d[i];
}

最后ans.size() 就是 LIS 的解

总代码:

int LIS(vector<int> d) {
	vector<int> ans;
	ans.push_back(d[0]);
	for (int i = 1; i < d.size(); i++) {
		if (ans[ans.size() - 1] < d[i]) ans.push_back(d[i]);
		else {
			int pos = lower_bound(ans.begin(),ans.end(),d[i]) - ans.begin();
			ans[pos] = d[i];
		}
	}
	return ans.size();
}

题型练习:[蓝桥杯 2020 国 B] 游园安排 - 洛谷

        题目背景:典型的LIS 变种问题
        题目描述:

                给定一个长字符串,其中每个大写字母搭配0到多个小写字母就是一个人名,列如: "SgadKHsdfa" 其中有三个人名,分别为: Sgad,K,Hsdfa。请输出人名的最长的不下降子序列。

                输入:WoAiLanQiaobei

                输出:AiLanQiaobei

        题目解析:

                1. 分割长字符串改为多个人名,形成人名字符串数组 v

string s;
cin >> s;
int f = 0;
vector<string> v;
for (int i = 1; i < s.size(); i++) {
    if (s[i] <= 'Z' && s[i] >= 'A') {
        v.push_back(s.substr(f,i - f));
        f = i;
    }
}
v.push_back(s.substr(f,s.size() - f)); // 注意最后一个人名特殊处理

             2. 对该字符串数组通过二分维系单调队列。

             3. 注意输出的答案并非是单调队列,单调队列上的每一个值只是长度为 i 的 LIS 元素结尾最小值,所以要记得将前缀值补充到最后一个字符串上去。(可能有点难懂这句话,详见代码)

            

int len = 1;
dp[len] = v[0]; ans[len] = v[0];
for (int i = 1; i < v.size(); i++) {
    int l = 1,r = len;
    int pos = 0;
    if (dp[r] < v[i]) pos = ++len;
    else {
        // 注意一定是用lower_bound() 题目描述是可能重名的
        pos = lower_bound(dp + 1,dp + len,v[i]) - dp; 
    }
    dp[pos] = v[i];
    ans[pos] = ans[pos - 1] + v[i]; // 记录前缀值,用于最后输出
}
cout << ans[len];
         整体代码:

                

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string ans[1000005],dp[1000005];
void solve() {	
	string s;
	cin >> s;
	int e,f = 0;
	vector<string> v;
	for (int i = 1; i < s.size(); i++) {
		if (s[i] <= 'Z' && s[i] >= 'A') {
			v.push_back(s.substr(f,i - f));
			f = i;
		}
	}
	v.push_back(s.substr(f,s.size() - f));
	int len = 1;
	dp[len] = v[0]; ans[len] = v[0];
	for (int i = 1; i < v.size(); i++) {
		int l = 1,r = len;
		int pos = 0;
		if (dp[r] < v[i]) pos = ++len;
		else {
			pos = lower_bound(dp + 1,dp + len,v[i]) - dp;	
		}
		dp[pos] = v[i];
		ans[pos] = ans[pos - 1] + v[i];
	}
	cout << ans[len];
}
int main() {
	ios::sync_with_stdio(false);
	int T = 1;
	while (T--) {
		solve();
	}
} 

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值