22.Acwing基础课第827题-简单-双链表

22.Acwing基础课第827题-简单-双链表

题目描述

实现一个双链表,双链表初始为空,支持 5 种操作:

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 k 个插入的数删除;
  4. 在第 k个插入的数左侧插入一个数;
  5. 在第 k 个插入的数右侧插入一个数

现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

输入格式

  1. 第一行包含整数 MM,表示操作次数。

    接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

    1. L x,表示在链表的最左端插入数 x。
    2. R x,表示在链表的最右端插入数 x。
    3. D k,表示将第 k 个插入的数删除。
    4. IL k x,表示在第 k 个插入的数左侧插入一个数。
    5. IR k x,表示在第 k 个插入的数右侧插入一个数。

输出格式

共一行,将整个链表从左到右输出。

数据范围

1≤M≤100000
所有操作保证合法。

输入样例:

10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

输出样例:

8 7 7 3 2 9

代码:

#include <iostream>
using namespace std;

// 数组模拟链表的最大长度
const int N = 1e6+10;

// e[i]   :节点 i 存储的值
// l[i]   :节点 i 的 左指针(左边节点的下标)
// r[i]   :节点 i 的 右指针(右边节点的下标)
// idx   :当前可以使用的 新节点下标
int e[N], l[N], r[N], idx;

// 初始化双向链表
// 0 表示左哨兵(最左头节点)
// 1 表示右哨兵(最右尾节点)
void init()
{
    // 左节点的右边是右节点
    r[0] = 1;
    // 右节点的左边是左节点
    l[1] = 0;
    // 0和1已被占用,下一个新节点从 2 开始
    idx = 2;
}

// 核心操作:在 节点a 的 右边 插入一个值为 x 的新节点
void insert(int a, int x)
{
    // 给新节点赋值
    e[idx] = x;
    // 新节点的左边 = a
    l[idx] = a;
    // 新节点的右边 = a 原来的右边节点
    r[idx] = r[a];
    // a 原来右边节点的左边 = 新节点
    l[r[a]] = idx;
    // a 的右边 = 新节点
    r[a] = idx++;
}

// 在 节点a 的 左边 插入 x
// 等价于:在 a 的 左节点 的 右边 插入
void insert_left(int a, int x)
{
    insert(l[a],x);
}

// 删除节点 a
// 注意:不能删除 0 和 1 哨兵节点
void remove(int a)
{
    // 让 a 右边节点的左边 直接指向 a 的左边
    l[r[a]] = l[a];
    // 让 a 左边节点的右边 直接指向 a 的右边
    r[l[a]] = r[a];
    // 节点 a 被从链表中剔除
}

// 遍历链表:从左到右输出所有值
void trasever()
{
    // 从左哨兵的右边开始遍历
    // 直到走到右哨兵 1 为止
    for(int i = r[0]; i != 1; i = r[i])
    {
        cout << e[i] << " ";
    }
    cout << endl;
}


int main()
{
    // m 个操作
    int m;
    cin >> m;
    
    // 先初始化链表
    init();
    
    int k,x;
    while(m--)
    {
        string op;
        cin >> op;
        
        // L x:在链表 最左边 插入 x
        if(op =="L")
        {
            cin >> x;
            insert(0,x);
        }
        
        // R x:在链表 最右边 插入 x
        else if(op == "R")
        {
            cin >> x;
            insert(l[1],x);
        }
        
        // D k:删除第 k 个插入的数
        // 因为节点从 2 开始,所以 k+1
        else if(op == "D")
        {
            cin >> k;
            remove(k+1);
        }
        
        // IL k x:在第 k 个数的 左边 插入 x
        else if(op == "IL")
        {
            cin >> k >> x;
            insert_left(k+1,x);
        }
        
        // IR k x:在第 k 个数的 右边 插入 x
        else if(op == "IR")
        {
            cin >> k >> x;
            insert(k+1,x);
        }
    }
    
    // 输出最终链表
    trasever();
    return 0;
}
#include <iostream>
using namespace std;

// 定义数组最大长度,100010是常用的略大于1e5的数值,避免越界
const int N = 100010;

int m;                  // 操作的总次数
// 数组模拟双向链表的核心结构:
// e[idx]:存储下标为idx的节点的值
// l[idx]:存储下标为idx的节点的左邻居下标
// r[idx]:存储下标为idx的节点的右邻居下标
// idx:当前可以分配的节点下标(相当于节点的"身份证号")
int e[N], l[N], r[N], idx;

// 功能:在节点a的右边插入一个值为x的新节点
// 参数:a - 目标节点下标,x - 要插入的数值
void insert(int a, int x)
{
    e[idx] = x;        // 第一步:给新节点赋值x
    l[idx] = a;        // 新节点的左邻居是a
    r[idx] = r[a];     // 新节点的右邻居是原来a的右邻居
    l[r[a]] = idx;     // 原来a的右邻居的左邻居改为新节点
    r[a] = idx ++ ;    // a的右邻居改为新节点,然后idx自增(分配下一个节点)
}

// 功能:删除下标为a的节点
// 参数:a - 要删除的节点下标
void remove(int a)
{
    l[r[a]] = l[a];    // 被删节点的右邻居的左邻居 → 被删节点的左邻居
    r[l[a]] = r[a];    // 被删节点的左邻居的右邻居 → 被删节点的右邻居
}

int main()
{
    cin >> m;  // 输入操作次数

    // 初始化双向链表的头尾哨兵节点:
    // 0作为左端点(头哨兵),1作为右端点(尾哨兵)
    // 初始时头哨兵的右邻居是尾哨兵,尾哨兵的左邻居是头哨兵
    r[0] = 1, l[1] = 0;
    idx = 2;   // 前两个下标(0、1)被哨兵占用,新节点从2开始分配

    while (m -- )  // 循环处理m次操作
    {
        string op;  // 存储操作类型(L/R/D/IL/IR)
        cin >> op;
        int k, x;   // k是操作的节点编号,x是要插入的数值
        
        if (op == "L")  // 操作L:在链表最左端插入数x(头哨兵0的右边)
        {
            cin >> x;
            insert(0, x);
        }
        else if (op == "R")  // 操作R:在链表最右端插入数x(尾哨兵1的左边)
        {
            cin >> x;
            insert(l[1], x);  // l[1]是尾哨兵的左邻居,即最后一个有效节点
        }
        else if (op == "D")  // 操作D:删除第k个插入的数对应的节点
        {
            cin >> k;
            remove(k + 1);   // 因为idx从2开始,第k个插入的节点下标是k+1(第1个→2,第2个→3...)
        }
        else if (op == "IL")  // 操作IL:在第k个插入的数的左边插入x
        {
            cin >> k >> x;
            insert(l[k + 1], x);  // l[k+1]是第k个节点的左邻居,插入到左邻居右边=插入到k节点左边
        }
        else  // 操作IR:在第k个插入的数的右边插入x
        {
            cin >> k >> x;
            insert(k + 1, x);
        }
    }

    // 遍历链表并输出所有有效节点的值:
    // 从头哨兵0的右邻居开始,直到遍历到尾哨兵1停止
    for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
    cout << endl;

    return 0;
}
posted @ 2026-03-31 14:30  CodeMagicianT  阅读(0)  评论(0)    收藏  举报