827. 双链表

本文介绍如何实现一个支持在任意位置插入、删除和定位操作的双链表数据结构,包括Lx、Rx、Dk、ILkx和IRkx等操作,通过实例演示并提供完整的C++代码实现,最后展示链表操作后的完整输出过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

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

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

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

共一行,将整个链表从头到尾输出。

TRICK

没有这个trick,我就没写出来。

  1. 双链表的特殊之处就在于,头尾节点和中间节点不同,可以让idx = 0,1 的节点充当头尾节点,从idx = 2的节点开始使用。
  2. 1和2,4和5,两个操作可以只用一个函数写,我写复杂了。
#include<iostream>
using namespace std;

const int N = 1e5+10;

int m;
int h,t,nel[N],ner[N],e[N],idx;//h是头节点,t是尾节点。

// 问题在于头节点怎么定义。
void left_insert(int x){
    e[idx]=x;
    nel[ner[h]]=idx;
    ner[idx]=ner[h];
    nel[idx]=h;
    ner[h]=idx++;
}

void right_insert(int x){
    // i 是最后一个点的编号
    e[idx]=x;
    ner[idx]=t;
    nel[idx]=nel[t];
    ner[nel[t]]=idx;
    nel[t]=idx;
    idx++;
}

void remove(int k){
    k++;
    ner[nel[k]]=ner[k];
    nel[ner[k]]=nel[k];
}

void insert_k_l(int k,int x){
    k++;
    e[idx]=x;
    ner[idx]=k;
    nel[idx]=nel[k];
    ner[nel[k]]=idx;
    nel[k]=idx;
    idx++;
}
void insert_k_r(int k,int x){
    k++;
    e[idx]=x;
    nel[idx]=k;
    ner[idx]=ner[k];
    nel[ner[k]]=idx;
    ner[k]=idx;
    idx++;
}

void init(){
    ner[0]=1;
    nel[1]=0;
    idx=2;
    h=0,t=1;
}
int main(){
    cin>>m;
    int x,k;
    init();//0,1号点分别是链表的头和尾节点,从idx=2的节点开始用
    while(m--){
        string s;cin>>s;
        if(s=="L"){
            cin>>x;
            left_insert(x);
        }
        else if(s=="R"){
            cin>>x;
            right_insert(x);
        }
        else if(s=="D"){
            cin>>k;
            remove(k);
        }
        else if(s=="IL"){
            cin>>k>>x;
            insert_k_l(k,x);
        }
        else{
            cin>>k>>x;
            insert_k_r(k,x);
            
        }
    }
    for(int i=ner[h];i!=1;i=ner[i]){
        cout<<e[i]<<" ";
    }
    return 0;
}
<think>好的,我需要解决用户的问题:如何在双链表中查找倒第二个节点。首先,我得回忆一下单链表双链表的基本结构差异。双链表每个节点有前驱和后继指针,这可能影响查找方法。 用户提到了查找倒第二个节点,这让我想到单链表中常用的快慢指针法,比如找倒第k个节点时,让快指针先走k步,然后快慢一起走,直到快指针到头。但双链表是否可以用类似的方法呢?或者有没有更简便的方式? 根据引用中的资料,单链表查找倒第k个节点可以用双指针法,例如引用[2]中的代码,通过两个指针的移动来定位。双链表由于有前驱指针,或许可以从尾部反向遍历。比如,先遍历到链表末尾,然后向前移动一个节点,就是倒第二个。不过这种方法需要先找到尾节点,再往回走,时间复杂度是O(n),间复杂度是O(1),但需要两次遍历:一次到尾节点,一次前移。 或者,是否可以使用类似单链表的双指针法,不需要反向遍历?比如,快指针先走一步,然后快慢指针一起走,当快指针到达末尾时,慢指针就在倒第二个节点。不过这可能和单链表的处理方式类似,但双链表的双向指针在这里可能并没有带来优势,因为查找倒第二个节点的逻辑和单链表相同,只是双链表的结构允许双向遍历。 另外,用户的问题可能希望利用双链表的特性来优化查找。例如,直接从尾节点前移一个节点。但前提是需要知道尾节点的位置。如果链表没有维护尾指针,还是需要从头遍历到尾,再前移一步。这时候时间复杂度是O(n),而如果维护了尾指针,可以直接访问尾节点的前驱,时间复杂度O(1)。但通常双链表可能不总是保存尾指针,这取决于具体实现。 所以,可能的解决方案有两种: 1. 常规的双指针法,和单链表一样,使用快慢指针,找到倒第二个节点。这种方法不需要利用双链表的特性,但同样有效。 2. 利用双链表的前驱指针,先找到尾节点,然后通过前驱指针找到倒第二个节点。这种方法需要遍历到尾节点,再前移一次。 现在需要比较这两种方法的优劣。第一种方法可能只需要一次遍历,而第二种方法需要先到尾节点,再前移,但两次遍历的时间复杂度都是O(n),只是常时间不同。例如,双指针法可能只需要遍历到倒第二个节点就停止,而第二种方法需要先到尾节点,再返回。对于长链表来说,可能双指针法更高效,因为第二种方法必须走到末尾,而双指针可能在快指针到达末尾时,慢指针已经到位。 不过,双链表的结构允许从后往前遍历,所以如果链表已经保存了尾节点,那么直接取尾节点的前驱即可,时间复杂度O(1)。但通常双链表实现不一定维护尾节点,所以需要考虑一般情况。 假设链表没有维护尾节点,那么常规的遍历方法是可行的。例如,遍历到末尾,然后返回前驱节点。这种情况下,代码可能如下: 初始化current指针指向头节点,然后循环直到current.next.next为null,此时current.next是尾节点,current就是倒第二个节点?或者需要更仔细的判断。 或者,另一种方法,遍历到链表末尾,记录前一个节点,即为倒第二个节点。 比如: current = head while current.next != null: prev = current current = current.next 此时,prev就是倒第二个节点。 这种方法只需要一次遍历,时间复杂度O(n),间O(1)。这可能比双指针法更直接,因为双指针法需要处理k=2的情况,而这里只需要遍历到末尾,同时记录前一个节点即可。 对于双链表来说,可能这种方法更简单,因为不需要处理快慢指针的步,直接遍历到尾节点,并保存前驱即可。而如果是双链表,且允许从后向前遍历的话,当找到尾节点后,直接取它的前驱指针即可,这样时间复杂度是O(n)(找到尾节点的时间)加上O(1)(取前驱)。所以总时间还是O(n),但可能比单链表的方法少一次遍历? 不过,不管是单链表还是双链表,找到倒第二个节点都需要遍历整个链表,除非维护了尾指针。例如,如果有尾指针,双链表可以直接取tail.prev,这是O(1)的操作。但如果没有维护尾指针,无论是单链表还是双链表都需要遍历到末尾。 用户的问题是关于双链表的,所以需要考虑双链表的特性。可能答案应指出,如果双链表维护了尾指针,可以直接通过tail.prev得到倒第二个节点,否则需要遍历到末尾,然后取前驱节点。 因此,算法实现可以分为两种情况: 情况一:双链表维护了尾指针。 直接访问tail.prev即可得到倒第二个节点。时间复杂度O(1)。 情况二:没有尾指针,需要遍历。 从头节点开始,遍历到一个节点,并记录前一个节点,即为倒第二个节点。时间复杂度O(n)。 但用户的问题可能是一般情况,即没有尾指针的情况。因此,需要给出遍历的方法。 比如,代码可能如下: function findSecondLastNode(head): if head is null or head.next is null: return null # 链表长度不足两个节点 current = head while current.next.next is not null: current = current.next return current 或者,更通用的方法,遍历到一个节点,同时保存前一个节点: function findSecondLastNode(head): if head is null: return null prev = null current = head while current.next is not null: prev = current current = current.next return prev # 当链表长度>=1时,prev可能是倒第二个节点,但需要判断链表长度是否>=2 不过需要考虑链表长度是否至少有两个节点。例如,如果链表或只有一个节点,返回null。 在双链表中,可能每个节点都有prev和next指针,所以另一种方法是,从头开始找到尾节点,然后通过尾节点的prev指针得到倒第二个节点。例如: function findSecondLastNode(head): if head is null: return null current = head while current.next is not null: current = current.next # current现在是尾节点 if current.prev is not null: return current.prev else: return null # 当链表只有一个节点时 这种方法利用了双链表的prev指针,不需要维护额外的指针,只需要遍历到尾节点,然后取prev即可。这样,时间复杂度是O(n)遍历到尾节点,然后O(1)取prev。 而单链表则无法这样做,必须通过遍历或者双指针法。而双链表的结构允许这种方法,所以这可能更优。 因此,对于双链表来说,查找倒第二个节点的优方法可能是: 1. 遍历到尾节点(如果不知道尾节点的位置)。 2. 然后访问该节点的prev指针,得到倒第二个节点。 这种方法的时间复杂度是O(n) + O(1) = O(n)。 因此,实现步骤如下: - 首先检查链表是否为,或者是否有至少两个节点。 - 遍历到尾节点。 - 返回尾节点的prev节点。 例如,代码: ListNode findSecondLast(ListNode head) { if (head == null) return null; ListNode current = head; while (current.next != null) { // 找到尾节点 current = current.next; } if (current.prev != null) { // 尾节点的前驱是倒第二个节点 return current.prev; } else { // 链表只有一个节点 return null; } } 这样,就可以在双链表中找到倒第二个节点。这利用了双链表的prev指针的特性,使得在找到尾节点后,可以直接得到前一个节点。 而如果是单链表,这种方法不可行,必须用其他方法如双指针或者记录前驱节点。 综上,双链表实现方法相对更简单,直接利用其结构特性。</think>### 双链表中查找倒第二个节点的实现方法 在双链表中查找倒第二个节点,可以充分利用其双向指针的特性。以下是具体实现思路和步骤: #### 方法一:利用尾节点的前驱指针(推荐) 1. **遍历到尾节点**:从头节点开始,沿着`next`指针遍历到链表末尾。 2. **访问前驱节点**:通过尾节点的`prev`指针直接获取倒第二个节点。 **时间复杂度**:$O(n)$(遍历到尾节点的时间),但实际仅需一次遍历。 **代码示例**(伪代码): ```python def find_second_last_node(head): if not head: return None current = head # 遍历到尾节点 while current.next: current = current.next # 返回倒第二个节点(尾节点的前驱) return current.prev if current.prev else None ``` #### 方法二:双指针法(通用方法) 若双链表未维护尾节点,也可采用类似单链表的双指针法: 1. **快指针先行一步**:快指针`fast`从第2个节点开始,慢指针`slow`从头节点开始。 2. **同步移动**:快慢指针同步移动,直到快指针到达末尾,此时慢指针指向倒第二个节点。 **时间复杂度**:$O(n)$,仅需一次遍历。 **代码示例**: ```python def find_second_last_node(head): if not head or not head.next: return None slow = head fast = head.next # 快指针遍历到末尾 while fast.next: slow = slow.next fast = fast.next return slow ``` #### 对比分析 - **方法一**更适合双链表,直接利用`prev`指针,逻辑更简洁。 - **方法二**在单链表双链表中通用,但未充分利用双链表特性。 ### 相关问题 1. 如何在单链表中高效查找倒第k个节点? 2. 双链表相比单链表插入和删除操作中有哪些优势? 3. 如果双链表维护了尾指针,如何优化查找倒第二个节点的操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值