可记录下标的链表——队列安排

洛谷题目如下:

题目描述

一个学校里老师要将班上 N 个同学排成一列,同学被编号为 1∼N,他采取如下的方法:

  1. 先将 11 号同学安排进队列,这时队列中只有他一个人;

  2. 2∼N 号同学依次入列,编号为 i 的同学入列方式为:老师指定编号为 i 的同学站在编号为 1∼(i−1) 中某位同学(即之前已经入列的同学)的左边或右边;

  3. 从队列中去掉 M 个同学,其他同学位置顺序不变。

在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

输入格式

第一行一个整数 N,表示了有 N 个同学。

第 2∼N 行,第 i 行包含两个整数 k,p,其中 k 为小于 i 的正整数,p 为 00 或者 11。若 p 为 00,则表示将 i 号同学插入到 k 号同学的左边,p 为 11 则表示插入到右边。

第 N+1 行为一个整数 M,表示去掉的同学数目。

接下来 M 行,每行一个正整数 x,表示将 x 号同学从队列中移去,如果 x 号同学已经不在队列中则忽略这一条指令。

输出格式

一行,包含最多 N 个空格隔开的整数,表示了队列从左到右所有同学的编号。

输入输出样例

输入 #1复制

4
1 0
2 1
1 0
2
3
3

输出 #1复制

2 4 1

说明/提示

【样例解释】

将同学 22 插入至同学 11 左边,此时队列为:

2 1

将同学 33 插入至同学 22 右边,此时队列为:

2 3 1

将同学 44 插入至同学 11 左边,此时队列为:

2 3 4 1

将同学 33 从队列中移出,此时队列为:

2 4 1

同学 33 已经不在队列中,忽略最后一条指令

最终队列:

2 4 1

【数据范围】

对于 20%20% 的数据1≤N≤10。

对于 40%40% 的数据,1≤N≤1000。

对于 100%100% 的数据,1<M≤N≤105。

这道题目考察链表,我的第一反应就是建立一个双向链表,然后遍历链表进行增删,最后输出

然后……

悲剧发生了

由于每次删除链表都要先把辅助指针放到头结点上,而且每次增删都要遍历一遍链表,这就导致了我写出了时间复杂度为o(2)的垃圾错误超时代码(TELwwwww)

#include<stdio.h>
#include<stdlib.h>
typedef struct lnode
{
	int sign;
	struct lnode* left, * right;
}lnode,*linklist;
void init(linklist* l)
{
	*l = (linklist)malloc(sizeof(lnode));
	(*l)->left = NULL;
	(*l)->right = NULL;
}
linklist setfirst(linklist* p)
{
    linklist q;
    q=(*p);
	while (q->left->left != NULL)
	{
		q = q->left;
	}
	return q;
}
void creatlist(linklist* l,int peopnum)
{
	int sub,before,count=1,j;
	linklist p,q;

	p = (linklist)malloc(sizeof(lnode));

	for (int i = 2; i <=peopnum;i++)
	{
		q = setfirst(l);
		scanf("%d", &before);
		for ( j = 1; j <= count; j++)
		{
			if (q->sign == before)
			break;
			q=q->right;
		}
		scanf("%d",&sub);
		if (sub == 0)
		{
			p->right = q;
			p->left = q->left;
			q->left->right=p;
			q->left = p;
			p->sign = i;
		}
		if (sub == 1)
		{
			p->left = q;
			p->right = q->right;
            q->right->left=p;
			q->right = p;
			p->sign = i;
		}

		p = (linklist)malloc(sizeof(lnode));
        count++;
	}
}
void del( int peopnum,int delsign,linklist *p)
{
	linklist q;
	q = (*p);
	for (int i = 1; i <= peopnum; i++)
	{
	    q=q->right;
		if (q->sign == delsign)
		{
			q->sign = -1;
		}

	}
}
int main()
{
	linklist l_r,p,q,l_l;
	int peopnum,delnum,delsign;
	scanf("%d", &peopnum);
	init(&l_r);
	init(&l_l);
	//建立两个空的头结点更好操作
	//分别连在首元结点的两边
	l_r->sign = -1;
	l_l->sign=-1;
	q=(linklist)malloc(sizeof(lnode));
	q->sign=1;
	q->right=l_r;
	q->left=l_l;
    l_r->left=q;
	l_l->right=q;

	creatlist(&q, peopnum);
	p = setfirst(&q);
	scanf("%d", &delnum);
	for (int i = 1; i <= delnum; i++)
	{
		scanf("%d", &delsign);
		del(peopnum, delsign, &p);
	}
	for (int i = 1; i <= peopnum; i++)
	{
		if (p->sign >= 0)
		{
			printf("%d ", p->sign);
			p = p->right;
		}
		else
			p = p->right;
	}
	return 0;
}

而且我也想不出什么优化时间复杂度的方案了(大佬求指点)

于是我看了看神犇的代码,我这个蒟蒻看了才知道,链表还能这么玩??

下面是我偷师自成的代码

#include<stdio.h>
#include<stdlib.h>
struct T
{
    int left;
    int right;
    int del;
};
struct T t[100010]= {0}; //初始化
void add(struct T* t,int before, int sub,int sign)
{
    if (sub == 0)
    {
        t[sign].right = before;
        t[sign].left = t[before].left;
        t[t[before].left].right = sign;
        t[before].left = sign;
    }
    else
    {
        t[sign].left = before;
        t[sign].right = t[before].right;
        t[t[before].right].left = sign;
        t[before].right = sign;
    }
}

void print(struct T* t, int peopnum)
{
    for (int i =t[0].right; i; i=t[i].right)
    {
        if (t[i].del==0)
            printf("%d ",i );
    }
}
int main()
{
    int peopnum,sub,delnum,delsign,before;
    scanf("%d", &peopnum);
    t[0].right=0;
    t[0].left=0;
    add(t,0,1,1);
    for (int i = 2; i <= peopnum; i++)
    {
        scanf("%d", &before);
        scanf("%d", &sub);
        add(t, before, sub, i);
    }
    scanf("%d", &delnum);
    while(delnum--)
    {
        scanf("%d", &delsign);
        t[delsign].del=1;
    }
    print(t,peopnum);
    return 0;
}

他是以数组为模型的链表,这样就完美实现了快速通过下标删除结点的操作,时间复杂度直接下降到常数级别(666666)

这让我不禁思考,我总是惯性的以为链表就是用结构体指针来实现的,但其实数组链表反而实现更轻松,用int也可以实现指针的操作。

看来脱离自己的思维惯性确实很难啊

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值