题目:acwing826
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 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
所有操作保证合法。
#include <iostream>
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
void init()
{
head = -1;
idx = 0;
}
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx++;
}
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m--)
{
char op;
int k, x;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];
else remove(k - 1);//第k个元素对应的索引为k - 1
}
else
{
cin >> k >> x;
add(k - 1, x);//第k个元素对应的索引为k - 1
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << " ";
cout << endl;
}
本题运用的是单链表,单链表顾名思义就是单向的链表。
链表作为一种数据结构,主要由value(数值)和next(指向)构成,next指向的是下一个数的位置,而不是下一个数的数值,这是必须要注意的。
本题中我们用两个数组来模拟单链表,数组e来存放输入的值,数组ne来存放指向下一个数的位置。
1.在初始化阶段,我们设置head=-1,index=0(索引值)
2.函数add_to_head实现向链表头插入一个数,我们正常的将值填入数组e,然后交换该数值与head的索引值,并且让head=idx,意思就是可以通过head来索引到新插入的值,然后再让索引值自增。
3.函数add实现向链表第k位后插入一个数,处理的过程与add_to_head大同小异,但是注意下面调用函数的时候需要写成k-1,这是由于索引值。首先在数组的下一位存入我们输入的值x,然后将x的next指针变为k的next指针,k的next指针指向x,然后使索引值自增。
4.函数remove实现删除链表的一个节点。初看时可能会感到疑惑,一个静态的链表怎么动态的删除一个数值呢?实际上题目说的删除和正常理解的删除有区别。比如说A-->B-->C,如果我们要把B删除,那么指针只需要绕过B就行了,A--(B)-->C,不用真的把B删除。比如说我们要删除A后面的B,那么我们就要把A的next指针指向C,就是原来B的next指针指向的位置。同样由于索引值的问题,在下面调用函数的时候我们需要微调。
5.主体函数的实现,前面就是简单的输入判断,最后的for循环值得注意:
for (int i = head; i != -1; i = ne[i]) cout << e[i] << " ";从head开始索引,更新方式为i=ne[i],就是next指针,i!=-1这是因为边界值。
具体的图解可以看这一篇AcWing 826. 单链表---图解 - AcWing
题目:acwing827
实现一个双链表,双链表初始为空,支持 55 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 k个插入的数删除;
- 在第 k个插入的数左侧插入一个数;
- 在第 k个插入的数右侧插入一个数
现在要对该链表进行 M次操作,进行完所有操作后,从左到右输出整个链表。
注意:题目中第 k个插入的数并不是指当前链表的第 k个数。例如操作过程中一共插入了 n个数,则按照插入的时间顺序,这 n个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
L x
,表示在链表的最左端插入数 x。R x
,表示在链表的最右端插入数 x。D k
,表示将第 k个插入的数删除。IL k x
,表示在第 k个插入的数左侧插入一个数。IR k x
,表示在第 k个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。
数据范围
1≤M≤100000
所有操作保证合法。
#include<iostream>
#include<algorithm>
using namespace std;
//双链表有两个指针,指向前+指向后
const int N = 1e6 + 10;
int e[N], l[N], r[N], idx;
void init() {
r[0] = 1, l[1] = 0;
idx = 2;
}
void add(int k, int x) {
e[idx] = x;
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
idx++;
}
void remove(int k) {
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main() {
init();
int M;
cin >> M;
string op; int x; int k;
while (M--) {
cin >> op;
if (op == "L") {
cin >> x;
add(0, x);
}
else if (op == "R") {
cin >> x;
add(l[1], x);
}
else if (op == "D") {
cin >> k;
remove(k + 1);
}
else if (op == "IL") {
cin >> k >> x;
add(l[k + 1], x);
}
else {
cin >> k >> x;
add(k+1, x);
}
}
for (int i = r[0]; i != 1; i = r[i])cout << e[i] << " ";
cout << endl;
return 0;
}
本题运用的是双链表,顾名思义就是可以双向索引的链表,既可以向前索引,也可以向后索引。
1.纵览全局,我们需要三个数组,数组e存放输入的数值,数组l存放左指针,数组r存放右指针
简言之就是 A-->B-->C(右指针),A<--B<--C(左指针)。
2.初始化阶段,初始位置只有右指针,没有左指针。让idx=2,这样会导致第k个插入的数的索引值为k+1。
3.add函数实现向第k个数之后添加x,向数组e中存入x,将x的右指针赋值为k的右指针,将x的左指针指向k,将第k位数向右指向的数的左指针指向x,将第k位数的右指针指向x,最后让idx自增。
结合论述举例在B之后插入D
A-->B-->C A<--B<--C
A-->B-->D-->C A<--B<--D<--C
4.remove函数实现的是删除数据,实际上本质思想和上面的忽略跳过是一样的。
将第k个数删除,那么帝k个数指向的左边的数的指向向右的数应该是第k个数指向向右的数,另一边也是同理可知。
主体函数的实现也是比较简单的。需要注意的是最后的for循环中i!=1指的是右指针的边界点