找出包含序列的所有元素的最小长度子序列

博客围绕给定序列,探讨找到包含其所有元素的最小长度子序列的方法。先给出题目描述,后介绍两种解题思想,一是暴力求解,时间复杂度高,面试难通过;二是使用哈希表,时间复杂度低,还给出了具体实现思路及参考链接。

1、题目描述

    给定一个序列如S = {1,8,2,1,4,1,2,9,1,8,4},我们需要找到包含S的所有元素的最小长度子序列(不重复,顺序不重要)。如何有效地找到这个子序列?注意:S:{1,2,4,8,9 }中有5个不同的元素,最小长度子序列必须包含所有这5个元素。

2、解题思想

(1)暴力求解(时间O(n^2),空间0)

      找出序列中的所有子串,判断每个子串是否包含所有元素。时间复杂度为O(n^2),这种做法面试通不过。

(2)使用哈希表(时间O(n),空间O(m),其中m为不同元素个数)

    首先遍历序列,找出序列中的有元素,用一个map来保存每个元素在序列中已经找到的个数,初时刻,这个map的所有元素的初始值全部为0。使用两个指针beginend分表保存序列的窗口,使用一个count变量保存已经找到的序列中不同元素的个数。

    从头开始遍历序列,遇到一个元素elem时,如果map[elem]的值为0,则map[elem]加1且count加1。如果map[elem]的值大于0,则map[elem]加1,count不变。当count的值等于序列中不相同的元素个数时,表示找到一个满足需求的子窗口,此时保存这个窗口的大小和起始位置。接着begin指针往后移动,直到map中有一个元素的map[elem]为0,然后count--,寻找下一个满足需求的窗口。代码实现如下:

#include<iostream>
#include<vector>
#include<map>
using namespace std;

/*
	找出包含序列的所有元素的最小长度子序列
*/
void MinLengthSeq(vector<int> input) {
	int begin = 0;                         //保存窗口的起始位置
	int count = 0;                         //保存已找到的不同元素的个数
	int windos = input.size();             //保存最小的窗口大小
	int start = 0;                         //保存所找到的最小窗口的位置
	map<int, int> has_found_elem;          //统计找到的每个元素的个数

	for (int i = 0; i < input.size(); i++)
		has_found_elem[input[i]] = 0;		

	for (int i = 0; i < input.size(); i++) {
		if (has_found_elem[input[i]] == 0) {     //如果找到一个不同的元素,count++,map中元素个数加一
			has_found_elem[input[i]]++;
			count++;
		}
		else                                     //如果找到相同的元素,count不变,map中元素个数加一
			has_found_elem[input[i]]++;        

		if (count == has_found_elem.size()) {    //如果count大小等于不相同的元素个数,则找到一个窗口
			if (windos > i - begin + 1) {
				windos = i - begin + 1;
				start = begin;
			}
			while (has_found_elem[input[begin]] > 0) { //begin指针往后移动机,直到map中有一个元素个数为0
				has_found_elem[input[begin]]--;		
				if (has_found_elem[input[begin++]] == 0)
					break;	
			}
			count--;                             //count--,去寻找下一个满足要求的窗口
		}
	}
	cout << "length:" << windos << endl;
	cout << "start:" << start << endl;

}

参考:https://blog.youkuaiyun.com/yangwenxue_admin/article/details/44568069

找出**最长上升子序列(Longest Increasing Subsequence, LIS)** 的长度,且子序列可以不连续,这是一个经典的动态规划问题。 --- ## ✅ 问题定义 给定一个序列(如字符串或整数数组),找出**最长的子序列**,使得其中的元素**严格递增**(也可以是非严格,视需求而定),并且不要求连续。 ### 示例: ```cpp 输入: [10, 9, 2, 5, 3, 7, 101, 18] 输出: 4 解释: 最长上升子序列为 [2, 3, 7, 18] 或 [2, 3, 7, 101],长度为 4。 ``` --- ## ✅ 解法一:动态规划(DP)—— O(n²) ### 原理: - 定义 `dp[i]` 表示以 `arr[i]` 结尾的最长上升子序列长度。 - 初始时每个 `dp[i] = 1`(自己单独成一个序列) - 对于每个 `i`,遍历前面所有 `j < i`,如果 `arr[j] < arr[i]`,则可以扩展序列: ```cpp dp[i] = max(dp[i], dp[j] + 1) ``` ### ✅ C++ 代码实现(适用于整型数组) ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; int n = nums.size(); vector<int> dp(n, 1); // 每个位置初始长度为1 for (int i = 1; i < n; ++i) { for (int j = 0; j < i; ++j) { if (nums[j] < nums[i]) { dp[i] = max(dp[i], dp[j] + 1); } } } return *max_element(dp.begin(), dp.end()); } int main() { vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; cout << lengthOfLIS(nums) << endl; return 0; } ``` ### 🔍 时间复杂度:O(n²) ### 🔍 空间复杂度:O(n) --- ## ✅ 解法二:二分优化 DP —— O(n log n) ### 原理: 维护一个数组 `tail`,其中 `tail[i]` 表示长度为 `i+1` 的递增子序列最小结尾元素。 我们遍历原数组,对每个元素: - 如果它大于 `tail` 最后一个元素 → 直接追加(延长序列) - 否则 → 使用二分查找找到第一个 ≥ 它的位置,替换(保持未来可能更长) 这个方法不能还原具体序列,但能正确计算长度。 ### ✅ C++ 代码实现(O(n log n)) ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; vector<int> tail; for (int x : nums) { auto it = lower_bound(tail.begin(), tail.end(), x); if (it == tail.end()) { tail.push_back(x); } else { *it = x; } } return tail.size(); } int main() { vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; cout << lengthOfLIS(nums) << endl; return 0; } ``` > ⚠️ 注意:这里用的是 `lower_bound`(非严格递增),如果是**严格上升**,应使用 `lower_bound` 并允许替换相等值。效果正确。 --- ## ✅ 如果输入是字符串? 例如:`"abcabcd"`,你想找字符按字典序递增的最长子序列。 只需将字符转为 ASCII 值即可处理: ```cpp string s = "abcabcd"; vector<int> nums; for (char c : s) nums.push_back(c); // 然后调用上面任意 LIS 函数 ``` --- ## ✅ 总结对比 | 方法 | 时间复杂度 | 空间复杂度 | 是否可打印路径 | |------|------------|-----------|----------------| | 动态规划(DP) | O(n²) | O(n) | ✅ 可通过回溯还原路径 | | 二分优化 DP | O(n log n) | O(n) | ❌ 路径信息丢失 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值