Leetcode:300. 最长上升子序列(获得所有最长子序列)

提示:本文的所有代码由C++实现,使用的开发工具是Visual Studio 2017


前言

本文对Leetcode:300. 最长上升子序列进行了扩展。原文求的是最长上升子序列的长度。本文在求出最长子序列长度之后,对所有最长上升的子序列进行了求解。本文只是给出一种实现方式,并不代表最优方式,欢迎各位同学参与讨论。


一、原题实现

1.题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

2.算法实现

使用O(n log n)时间复杂度的算法对该题目进行实现。基本实现思路是使用一个列表记录一个上升序列。如果新元素大于列表尾部元素,则push进入列表,否则使用lower_bound函数搜索>=新元素的位置,并更新该位置的值为新元素。

int  findLongestSequence(vector<int> &s1) {
	int n = s1.size();
	vector<int>  box;
	for (int i = 0; i < n; i++) {
		int c = s1[i];
		if (box.empty() || box.back() < c) {   
		    //添加到列表结尾
			box.push_back(c);
		}
		else {
			 //更新列表位置>=新元素的元素为新元素,不增加列表长度   
			*lower_bound(box.begin(), box.end(), c) = c; 
		}
	}	
	return box.size();

3.结果分析

该算法可以很好的把最长上升子序列的长度计算出来,但是box内存储的并不一定是有效的上升子序列。例如输入序列2 5 1 7 9 10 2 3,运行结束时返回的最大长度是5,box内存储的数字是1 2 3 9 10。很明显,box内存储的不是原序列的一个最长上升子序列。为了更有效的获得最大长度后面的2 3对box内数字进行了更新。下面会在该算法的基础上进行扩展,通过dfs搜索所有最长上升子序列。

二、算法扩展

1.增加记忆数组

增加记忆数组rem,用来记录获得最长上升子序列的过程中所有在box中出现的下标。例如输入序列2 5 1 7 9 10 2 3,的有效下标记录为
2 6 7
0 1 3 4 5

int  findLongestSequence(vector<int> &s1) {
	int n = s1.size();
	vector<int>  box;
	vector<vector<int>>  rem; //增加记忆数组
	for (int i = 0; i < n;i++) {
		int c = s1[i];
		if (box.empty() || box.back() < c) {
			box.push_back(c);
			rem.push_back({i}); //记忆记录新增加元素的下标
		}
		else {
			vector<int>::iterator find = lower_bound(box.begin(), box.end(), c);
			*find = c;
			rem[find - box.begin()].push_back(i); //记忆数组记录新的下标
		}
	}

	//有效路径搜索部分
	vector<vector<int>>  res;
	vector<int>  find;
	//对有效路径进行搜索,并记录下标
	dfs(s1, rem, res, find);
	//调试:展示有效序列下标和有效序列
	showRes(s1, res);
	return box.size();
}

2.搜索所有有效上升序列

使用dfs搜索所有最长有效上升序列的下标,搜索规则有以下几点。

  1. 搜索结果长度等于最长子序列长度时,搜索结束
  2. 新的下标大于搜索队尾下标
  3. 新的下标对应的数字大于队尾下标对应的数字
/*
s1 原数组
rem 对过程下标的记录数组
res 所有上升序列的返回结果
find  搜索过程保存数组
*/
void  dfs(vector<int> &s1, vector<vector<int>>  &rem,vector<vector<int>> &res,vector<int> &find) {
	int idx = find.size();
	if (rem.size() == idx) { 
		//搜索数组长度等于最大序列长度时,添加该路径
		res.push_back(find);
		return;
	}
	for (auto i : rem[idx]) {
		//添加到搜索队列的条件,见上面2、3
		if (find.empty() || find.back() < i && s1[find.back()]<s1[i]) {
			find.push_back(i);
			dfs(s1, rem, res, find);
			find.pop_back();
		}
	}
}

3.结果展示

例如输入序列2 5 1 7 9 10 2 3
结果为:
Show result:
Index       is:0 1 3 4 5
Squence is:2 5 7 9 10

输入序列2 5 1 7 9 10 2 3 4 5
结果为:
Show result:
Index       is:0 1 3 4 5
Squence is:2 5 7 9 10

Index       is:2 6 7 8 9
Squence is:1 2 3 4 5

void   showRes(vector<int> &s1,vector<vector<int>> &res) {
	cout << "Show  result:" << endl;
	for (auto r : res) {
		cout << "Index   is:";
		for (auto i : r) {
			cout << i << " ";
		}
		cout << endl;
		cout << "Squence is:";
		for (auto i : r) {
			cout << s1[i] << " ";
		}
		cout << endl;
		cout << endl;
	}
}

总结

因为有序列长度、下标升序和数字升序的限制,这个dfs的时间复杂度应该不是很高。有兴趣的同学可以帮忙算一下。

三、思路补充(2021-03-19)

最近又学到了一种使用pre数组记录dp路径的方法,因为上面O(nlog(n))的算法会对过程信息压缩。所以想要记录路径需要O(n2)时间复杂度的算法。下面是对该算法的实现。

int lengthOfLIS(vector<int>& nums) {
	int n = nums.size();
	if (n == 0) return 0;
	vector<int>  dp(n,1);
	//记录上升序列下标
	vector<int>  pre(n, -1);
	//记录最长上升序列最后的下标
	int lastIdx = -1;
	int maxLen = 0;
	for (int i = 1; i < n; i++) {
		for (int j = i - 1; j >= 0; j--) {
			if (nums[i] > nums[j] && dp[j] + 1 > dp[i]) {
				dp[i] = dp[j] + 1;
				pre[i] = j;
			}
		}
		if (dp[i]>maxLen) {
			maxLen = dp[i];
			lastIdx = i;
		}
	}

	//通过回溯法查找一个最长上升序列
	vector<int> res(maxLen);
	int id = maxLen - 1;
	while (lastIdx != -1) {
		res[id--] = nums[lastIdx];
		lastIdx = pre[lastIdx];
	}

	//打印上升序列
	for (auto r : res) {
		cout << r << " ";
	}
	cout << endl;

	return maxLen;
}

输入序列2 5 1 7 9 10 2 3 4 5
结果为:
Squence is:2 5 7 9 10
maxLen is:5

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值