【编程题】有序数列多次抽取数字放最前,最后输出序列。


这是前几天做的一道笔试题,当时实现复杂了,导致最后超时,现在记录一下思路与实现方法。

题目

有一个长度为n并包含1~n的整数序列。在最初,序列为升序排列,即1在序列首部,n在序列末尾。
对序列进行m次操作,第i次操作会把 x i x_i xi从序列中取出并放到序列首部。经过m次操作后,序列变成什么样子?

输入描述

输入包含m+1行。
第一行包含两个正整数n,m(1<=n,m<=100000)。
接下来m行,每行一个正整数 x i x_i xi,表示每次操作的数。

输出描述

输出一行,即m次操作之后的序列,以空格分割,行末无空格。

示例
输入

5 4
4
3
1
4

输出

4 1 3 2 5

先来说一下解决这道题的几种思路:

思路1 哈希表

这也是我在笔试时使用的方法。
先通过一个遍历生成一个哈希表,key为具体数字,value为每个数字在序列中的索引。这样做的好处是每次搜索要操作数x时,搜索时间复杂度为O(1)。找到数x后,记录它当前的索引index,然后将x的索引值变为-1。接着遍历整个哈希表,将所有数的索引值小于x最初索引index的索引数+1,这样就得到每次操作之后,每个数的新索引。经过m次操作之后,再次遍历整个哈希表,根据每个数的索引值构建一个序列,最后将其输出即可。

可以发现这个思路多次使用了遍历,从而导致我最后提交只有70%通过,其余都超时了。时间复杂度为O(m*n+3n),空间复杂度至少为O(2n)。代码如下:

#include<iostream>
#include<vector>
#include<unordered_map>

using namespace std;

int main() {
	int n, m = 0;
	scanf_s("%d", &n);
	scanf_s("%d", &m);
	unordered_map<int, int> hashmap;
	for (int i = 0; i<n; i++) {
		hashmap.insert(pair<int, int>(i + 1, i));
	}
	for (int i = 0; i<m; i++) {
		int j = 0;
		scanf_s("%d", &j);
		int temp_index = hashmap[j];
		hashmap[j] = -1;
		for (auto it = hashmap.begin(); it != hashmap.end(); ++it) {
			if (it->second<temp_index) {
				hashmap[it->first] = it->second + 1;
			}
		}
	}
	vector<int> result;
	result.resize(n);
	for (auto it = hashmap.begin(); it != hashmap.end(); ++it) {
		result[it->second] = it->first;
	}
	for (int i=0; i<result.size(); i++){
		cout << result[i];
		if (i != n - 1) {
			cout << " ";
		}
	}
	return 0;
}

思路2(移位与查找操作合并)相对最优

这是我和他人经过讨论后,得出的一种更高效的方法。
首先通过一个遍历将所有数存放如vector中,每次操作一个数x时,直接将x与vector[0]中的值替换,取出的值放入temp中,接着temp依次与后面的数进行替换,直到取出的数与x相等,则结束替换。最后将其输出即可。
优点:某种程度上将移位与查找操作合并在了一起,节省了时间。
时间复杂度最坏情况为O(m*n + 2n),空间复杂度为O(n)。代码如下:

#include<iostream>
#include<vector>

using namespace std;

int main() {
	int n, m = 0;
	scanf_s("%d", &n);
	scanf_s("%d", &m);
	vector<int> result(n);
	for (int i = 0; i<n; i++) {
		result[i] = i + 1;
	}
	for (int i = 0; i<m; i++) {
		int x = 0;
		scanf_s("%d", &x);
		if (x == result[0]) {
			continue;
		}
		int pre = result[0];
		result[0] = x;
		for (int i = 1; pre != x; i++) {
			int temp = result[i];
			result[i] = pre;
			pre = temp;
		}
	}
	for (int i=0; i<n; i++){
		cout << result[i];
		if (i != n - 1) {
			cout << " ";
		}
	}
	system("pause");
	return 0;
}

思路3 链表

使用链表,用链表存储每个数。
优点:数字位置可以高效移动,无需改变其他元素位置。
缺点:1.生成链表会占用大量内存。2.链表只能顺序查找,会花费较多时间。
代码略。

思路4(放弃索引移位,直接判断输出)

这是我写这篇文章的时候突然想到的(/ω\),这种方法时间/空间复杂度可能很高-_-||
既然我们知道了有m个数,那么我们直接反转m个数,并排除掉重复的数,存入vector,先把这几个数输出。其余的数每次输出前判断是否在vector,不在则按顺序输出即可。
时间复杂度:O( m + ( m − 1 ) ∗ ( m − 2 ) 2 + n ∗ m = m 2 + m 2 + n ∗ m m+\frac{(m-1)*(m-2)}{2}+n*m=\frac{m^2+m}{2}+n*m m+2(m1)(m2)+nm=2m2+m+nm )
空间复杂度:O(m)

#include<iostream>
#include<vector>

using namespace std;

int main() {
	int n, m;
	scanf_s("%d", &n);
	scanf_s("%d", &m);
	vector<int> m_list;
	vector<int> m_one;
	int x = 0;
	for (int i = 0; i<m-1; i++) {
		scanf_s("%d", &x);
		m_list.push_back(x);
	}
	scanf_s("%d", &x);
	m_one.push_back(x);
	cout << x << " ";
	vector<int>::iterator it;
	for (int i = m-2; i>-1; --i) {
		int y = m_list[i];
		it = find(m_one.begin(), m_one.end(), y);
		if (it != m_one.end()) {
			continue;
		} else {
			m_one.push_back(y);
			cout << y << " ";
		}
	}
	int t = 0;
	for (int i=0; i<n; i++){
		int c = i + 1;
		if (t < m_one.size()) {
			it = find(m_one.begin(), m_one.end(), c);
			//如果找到c,则计数并进行下一次循环
			if (it != m_one.end()) {
				t += 1;
				continue;
			}
		}
		cout << c;
		if (i != n - 1) {
			cout << " ";
		}
	}
	system("pause");
	return 0;
}

总的来说,还是思路2最好,其余几种都会存在超时的情况,尤其是思路4-_-||。
如果有更好的方法,欢迎大家留言交流。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值