前言
一道双链表模板题目,STL能不能过我不清楚,这边用数组模拟(简单数据结构尽量使用数组模拟,这样速度更快),单链表和双链表的思想其实都很好理解,但是手动用数组模拟实现的话对于边界的处理还是有一定讲究的(有点像二分),很容易写死循环,建议背一个模板(我背的是acwing上的),借此机会加深一下自己对边界处理的解。
题目概述
AC代码
#include <iostream>
using namespace std;
const int N = 100010;
int e[N], l[N], r[N], idx, del[N];
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
int main()
{
r[0] = 1, l[1] = 0, idx = 2;
insert(0, 1);
int n;
cin >> n;
for(int i = 2; i <= n; ++i)
{
int k, q;
cin >> k >> q;
if(q)
insert(k + 1, i);
else
insert(l[k + 1], i);
}
int m;
cin >> m;
while(m--)
{
int a;
cin >> a;
del[a] = 1;
}
for(int i = r[0]; i != 1; i = r[i])
{
if(!del[e[i]])
cout << e[i] << " ";
}
return 0;
}
分析思路
- 题目中要求多次进行插入和删除,而且是左插右插都有,1e5的话只能考虑链表了,链表进行一次插入操作的时间复杂度为O(1)。
- e[]存放节点的值(这道题就是学生的编号),l[]代表左边节点在数组中的下标,r[]代表右边节点在数组中的下标. 注意数组的下标和学生编号要区分清楚。 idx在这里代表下一个要用的数组空间的下标.
- 初始化要做清楚。
r[0] = 1;
l[1] = 0;
idx = 2;
个人认为这个模板比较好的一个地方,下标0和1这两个位置不作为实际存储,真正使用的空间是从idx = 2开始。下标为0的右边永远是链表第一个节点,下标为1的左边永远是链表最后一个节点,这样能够把边界处理的很清楚。另外有一个粗浅的检查初始化是否合理的方式,就是看最后的输出语句能否在不对链表做任何操作的情况下给出正确反映。如这里的:
for(int i = r[0]; i != 1; i = r[i])
{
....
}
即使不做出任何插入删除操作,这个循环也能正常结束(第一次检查条件就退出),r[0]是第一个节点,一开始这个节点为1(1表示空),所以这个初始化条件起码不会死循环,那么具体细节自己再用笔模拟一下看看能否自洽。
4.插入环节
for(int i = 2; i <= n; ++i)
{
int k, q;
cin >> k >> q;
if(q)
insert(k + 1, i);
else
insert(l[k + 1], i);
}
i从2循环到n,刚好可以对应插入学生的编号,insert操作表示在下标为k的节点右插,但是我们上边实际是从idx = 2开始的,也就是说第一个插入的数是下标为2,第二个插入的就是下标为3,以此类推,所以这里右插调用的参数是k + 1而非k。那么如果是左插呢?那就是对 k + 1节点的左边那个节点右插,所以传l[k+1], 注意这里容易混淆,是k+1的左边,而不是直接把k+1-1,因为我们模拟链表,下标相对位置不等同实际模拟的站队位置。
5.删除环节 可以自己再写一个删除操作的函数,但是这里有个简便做法,考虑编号不超过1e5,可以用del散列 标记,下标为编号,这样无需真的删掉节点,只要输出的时候做判断该节点要不要输出即可。
文末广告
学习算法和数据结构真的是个很累的过程,不会做只能求助于题解。 因为写代码这个东西基本上是千人千面。同时网络上搜到的题解很多要么用到的是自己还没学到的知识,看不懂;要么内核过于简陋,只能糊弄当前题目,不具有普适性。
如果你是一个喜欢做洛谷,ACwing和PTA的题目的同学,欢迎关注我的博客,我主要在这三个平台上做题,认为有价值和有难度的题目我会写题解发布出来。
TreeTraverler的往期文章