双向排序--栈的应用

分析:如果单纯只用排序的思维来解这道题的话,无论什么排序,他的时间复杂的都不能完全满足题目要求。所以需要考虑优化操作步骤。就是尽量减少升序和降序的操作。首先,当重复遇到降序操作时,那么只有这两次降序中的最大降序范围有效,比如对123456从1到3降序与1到4降序操作,两次操作后其实只有1到4有效,同样的道理两次连续的升序操作也只有范围大的有效。因此在遇到连续的0或者1时就需要将两次操作进行合并,例如

05
16
04
06
13
17
07

左边的操作可以简化为右边的操作,即将连续的操作进行合并,保留范围大的操作。

05
16
06
13
07

 

 

现在的操作就变成了010101类型,就是先降序再升序,由于序列本身一开始就是升序,所以第一次必为降序才有效,否则无效, 所以操作顺序一定是 01010101类型的。

那么可以把01看着一次循环,经过一次01操作,最左边一定是降序,最右边一定是升序。

如果下一次0操作范围比上一次0操作范围大,那这次的降序操作就使得前两次的操作相当于没做。

比如    1 2 3 4 5 6 7 8

0 4操作:4 3 2 1 5 6 7 8

1  3:      4 3 1 2 5 6 7 8

再进行 0 5,这次操作范围比上一次相同操作0 4的范围要大:5 4 3 2 1 6 7 8,就相当于未进行前两步操作,直接就进行的 0 5操作。因此对于0操作的范围必须逐渐减小。

同理 进行010后,再进行1操作,这个操作范围一定比上次1操作范围大,否则前两次都会无效。

比如  1 2 3 4 5 6 7 8

0 6:6 5 4 3 2 1 7 8

1 4:6 5 4 1 2 3 7 8

0 5:6 5 4 2 1 3 7 8

1 3:6 5 1 2 3 4 7 8

1 3是从3到末尾的升序比上一次1 4范围大,所以就相当于前两次没有操作,他就等效于只进行了 0 6和1 3操作。

综上所述,要简化操作,就得分两步,第一步合并连续的操作,第二步判断当前操作范围是否大于前一次相同操作范围,是的话就前两次操作无效。

那么具体代码上我们可以用栈的思维,进栈和出栈都再栈顶进行操作,维护两个栈p和q,只要p,q同进同出,就能让两个数在栈的同一高度。

如果前一次操作与当前操作相同,就合并当前操作,在进行判断当前操作与上一次相同的操作范围的大小,如果大,就一直连续出栈两次,并并不断循环,直到比前一次范围小。

经过以上操作,我们就能得到最简化的操作p q,他们大概就是0操作的q值逐渐减小,1操作的q值逐渐增大。大概如下图

此时对这样简化了的操作直接排序依然不能直接满足时间复杂度,所以还有对排序进行优化 。

经过一轮01操作后,右边降序左边升序。那么我们可以先把两端确定了。

比如1 2 3 4 5 6 7 8

经过 一轮0 6 和1 3操作后就变成了  6 5 1 2 3 4 7 8。我们发现这个操作后在第3到第6位是1 2 3 4连续递增的。对他再进行一轮 0 5 ,1 4操作:6 5 321478  6 5 3 1 2 4 7 8,在第4位和第5位是连续的。发现最开始的 65 位置没变,4 7 8也位置也没变,实质上可以通过图也能看出来,每次改变的都是上一次重合的部分。未重合部分的顺序没有改变。因此我们可以每一次都可以把未重合部分的数字给确定下来。具体步骤见代码。

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

int max(int a, int b)
{
	if (a > b)
		return a;
	return b;
}
int min(int a, int b)
{
	if (a < b)
		return a;
	return b;
}
int main()
{
	int n, m;
	cin >> n >> m;
	vector<int>arr;
	vector<int>p;
	vector<int>q;//我用容器代替栈,方便访问数据。
	int a, b;//用来输入当前操作的p q值
	for (int i = 0; i < n; i++)
	{
	arr.push_back(i + 1);
	}
	while(m--)
	{
		cin >> a >> b;
		if (a == 0)//当前操作为0的情况
		{
			if (p.size() == 0)//如果栈中没有数,就直接将当前操作入栈。
			{
				p.push_back(a);
				q.push_back(b);
			}
			else 
			{
				if (p.back()== 0)//上一次操作也为0即两次连续的00
				{
					b = max(q.back(), b);//合并上一次操作到当前操作,q值越大范围越大。
					p.pop_back();//上一次操作已经合并到当前操作,所以将上一次操作出栈。
					q.pop_back();
				}
				while (p.size() >=2 && q[q.size() - 2]<=b)//判断当前操作范围是否比上一次操作范围大,是的话连续出栈两次。010的情况
				{
					p.pop_back();
					p.pop_back();
					q.pop_back();
					q.pop_back();
				}
					p.push_back(a);
					q.push_back(b);
			}
		}
		else
		{
			if (p.size() == 0)//第一操作为1时不存入栈。
			{
				continue;
			}
			if (p.back() == 1)//11的情况,合并当前操作
			{ 
				b=min(q.back(), b);
				p.pop_back();
				q.pop_back();
			}
			while (p.size() >=2 && q[q.size() - 2] >= b)//101的情况,判断当前操作是否比上一次相同操作范围大。是的话出栈两次并不断循环。
			{
				p.pop_back();
				p.pop_back();
				q.pop_back();
				q.pop_back();
			}
				p.push_back(a);
				q.push_back(b);
		}
	}
	int r_pos = n - 1, l_pos = 0;//定义左右端点
	for (int i = 0; i < p.size(); i++)
	{
		if (p[i] == 0)//0操作的情况
		{
			while (r_pos > q[i]-1) {  //右边是升序,从右边位置到q相当于从n到q逐渐减小   ,n是从最大值开始赋值,赋值一个n少一个所以n不断减少。
				arr[r_pos] = n;
				r_pos--;
				n--;
				if (l_pos > r_pos)
				{
					break;
				}
			}
		}
		else {
			while (l_pos < q[i]-1) {//左边是降序从左边到q也相当于逐渐减小。
				arr[l_pos] = n;
				l_pos++;
				n--;
				if (l_pos > r_pos)
				{
					break;
				}
			}
		}
		if (l_pos > r_pos) {
			break;
		}
	}

	if (p.back()==0) { //最后次是降序的情况
		while (l_pos <= r_pos) {//如果左右位置还没重合,说明中间还有位置没有确定,经过推算中间位置是从左到右逐渐降序
			arr[l_pos] = n;
			l_pos++;
			n--;
		}
	}

	else {
		while (l_pos <= r_pos) {//这种情况下是从右到左逐渐降序
			arr[r_pos] =n;
			r_pos--;
			n--;
		}
	}
	for (int i = 0; i < arr.size(); i++)
	{
		cout << arr[i] << " ";
	}
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值