摘要:
用数组模拟静态链表。
题目
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 $k$ 个插入的数后面的数;
- 在第 $k$ 个插入的数后插入一个数
现在要对该链表进行 $M$ 次操作,进行完所有操作后,从头到尾输出整个链表。
注意: 题目中第 $k$ 个插入的数并不是指当前链表的第 $k$ 个数。例如操作过程中一共插入了 $n$ 个数,则按照插入的时间顺序,这 $n$ 个数依次为:第 $1$ 个插入的数,第 $2$ 个插入的数,…第 $n$ 个插入的数。
输入格式
第一行包含整数 $M$,表示操作次数。
接下来 $M$ 行,每行包含一个操作命令,操作命令可能为以下几种:
H x
,表示向链表头插入一个数 $x$。D k
,表示删除第 $k$ 个输入的数后面的数(当 $k$ 为 $0$ 时,表示删除头结点)。I k x
,表示在第 $k$ 个输入的数后面插入一个数 $x$(此操作中 $k$ 均大于 $0$ )。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
- $1≤M≤100000$
- 所有操作保证合法。
输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
何为链表
首先什么是链表呢,链表是一堆离散的结点,每个结点存了两个值,一个表示结点的数值,一个是 $next$ 指针,它指向了下一个结点的位置。链表是一个递归的数据结构(俄罗斯套娃),如果给我们一个链表的头结点,我们可以遍历整个链表。可以认为链表某结点的 $next$ 指针指向了其后面整个链表。
一个链表结点可以形象地看成:
[value]
[next ]
用数组模拟链表,一定要理解其本质。
数组模拟链表的本质
我们用一个数组 $A$ 来存储数值,一个数组 $B$ 来存储指针。一个链表结点分别由同一下标的两个数组元素构成。因为是用数组模拟链表,所以结点的指针用数组下标表示。
从 $0$ 开始遍历两个数组,$0$ 代表第 $0$ 次插入操作,每一次向链表中新插入数值,下标后移一位,相当于new一个新的结点。但是当删除操作时,下标不会移动。也就是说,删除结点时,结点依然在数组中,只是链表不会指向它。
另外,需要一个下标来表示整个链表的头结点,注意,头结点不一定在 $0$ 位置(可能会在头结点前面插入新的结点)。
为什么要有头结点下标呢?这样可以很方便地对整个链表遍历,也很方便地在头结点出进行插入和删除操作。
思路
向链表头插入一个数: 即更新头结点。在值域数组新的下标处放入插入的数的值,插入的结点指向了头结点位置,数组下标后移一位方便下次操作。
删除第k个输入的数后面的数: 当 $k$ 为 $0$ 时,删除头结点,只需将 $head$ 移动到 $head$ 指向的结点;$k$ 不为 $0$ 时,删除第 $k$ 次插入的结点后面的结点,因为题目中描述“第 $k$ 次插入”是从 $1$ 开始计算的,所以此处需要删除下标为 $k-1$ 的结点后面的结点。相当于是链表从 $k-1$ 结点跳过它后面的结点,指向它后面的后面的结点。
在第k个输入的数后面插入一个数: 题目说明此处 $k$ 大于 $0$。只需在值域数组新的下标处放入新插入的数的值,在指针域数组新的下标处放入下标为 $k-1$ 的结点指向的下一个结点下标,再把下标为 $k-1$ 的结点指向新的下标。另外,数组下标后移一位方便下次插入操作。
代码
1 |
|
1 | /* |
e[N]
和ne[N]
数组分别为链表结点的值域数组和指针域数组,head
为链表头结点下标,idx
为两个数组共用的游标,每当需要往链表中插入值时,idx
后移一位,相当于new
一个新结点。head
初始化为-1,表示初始状态链表头结点为null
;idx
初始化为0,表示将在两个数组的起始位置添加结点。因为题目的第「k
」个数指的是从1
起 第k
个操作数,所以如果我们idx
初始化为0
,那么k
和数组下标的对应关系是k - 1 == idx
。接着我们注意到算法在使用k
时是直接当数组下标用的,那是因为在7
8
处调用函数时,参数已经做了修正(k - 1
)。当然可以让idx
初始化为1
,那么k
和idx
完全对应,可以直接使用,且调用函数时不需要修正。new
一个新的结点,让结点的next
指针指向head
,游标后移。new
一个新的结点,让结点的next
指针指向下标k
的下一位,游标后移。- 跳过下标为
k
的结点。- 注意到题干要求当
k
为0的情况。- 注意题干的第
k
个结点是从1开始计数的,我们算法实现的k
是下标k
。- 同上。
- 链表的遍历: 从头结点下标开始
i = head
,到null
结点结束i == -1
。因为第一个操作必然是更新头结点(否则其他操作没法进行),接着将头结点指向-1(null)
,即第一步必然是head -> null
,随后所有操作都在head
和null
之间进行。那么在所有操作结束之后,遍历链表,一定是以null
结束。
注意到题目说明:
第 $k$ 个插入的数并不是指当前链表的第 $k$ 个数。例如操作过程中一共插入了 $n$ 个数,则按照插入的时间顺序,这 $n$ 个数依次为:第 $1$ 个插入的数,第 $2$ 个插入的数,…第 $n$ 个插入的数。
那么可以直接初始化 idx
为1,表示从第一个插入的数开始。
见2021-6-29更新的第二版代码。
原题链接: Acwing 826. 单链表