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

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

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

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

vector 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 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 d) {
	int maxn = 1;
	vector 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 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 {
    // upper_bound() 二分找到ans数组中大于d[i]的第一个位置
	int pos = upper_bound(ans.begin(),ans.end(),d[i]) - ans.begin(); 
	ans[pos] = d[i];
}

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

总代码:

int LIS(vector d) {
	vector 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 = upper_bound(ans.begin(),ans.end(),d[i]) - ans.begin();
			ans[pos] = d[i];
		}
	}
	return ans.size();
}

你可能感兴趣的:(c++,开发语言)