前言

在上篇中,小莱给大家分享了链表的相关知识及初步进阶。接下来几期里,小莱计划将链表的相关拓展写成一个系列。


画外音:关于链表相关知识,请点击回顾


今天,我们先来看第一道拓展题。


题目:给定一个链表,删除链表倒数第n个节点,并且返回链表的头结点。


示例:给定一个链表:1->2->3->4->5,和 n = 2。当删除了倒数第二个节点后,链表变为 1->2->3->5。


说明:给定的n保证是有效的。

链表长度法

相信很多人看到这道题时的第一想法都是使用链表长度来进行处理。即先获取链表长度,然后找到指定节点进行删除。


那么我们先来看下这种处理方式。


1、已知链表长度



根据《图解:链表基础知识及反转》中实现的链表结构(如图所示),哨兵节点中的链表长度是已知的,那么删除倒数第N=2节点时就很方便操作了。



只需将倒数第3个节点的指针域指向倒数第1个节点即可。这样我们采用一次遍历就可以删除倒数第N个节点了,即找到p节点,操作p->next=p->next->next。


2、未知链表长度



但是如果是没有存储长度的链表,我们如何来处理呢?


聪明的你可能会说,那还不简单。先扫描一遍链表获取到长度,然后再根据已知链表长度来处理不就行了?


画外音:面试中如果使用了这种方式,那么恭喜你,可以回家等通知了!


显然这样的做法更低效,要遍历两次链表才能完成。

前后指针法

那么有没有更好的方式呢,在未知链表长度情况下,只用一趟就能进行处理(这也正是此题的进阶要求)?


进阶:在链表长度未知情况下,使用一趟扫描实现


如图,我们使用前后指针的方法定义了两个指针变量p和q。其中q为后指针,p为前指针。



...............


初始时,两个指针同时指向哨兵节点。q先向前走n个节点后,p指针从头节点出发,两个指针一起向前遍历。当q.next指向NULL时,p指向倒数第n+1个节点。此时将p->next = p->next->next即可将倒数第N个节点删除。


代码实现:


struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    int i = 0;
    struct ListNode* p,* q;
    p = q = (struct ListNode*)malloc(sizeof(struct ListNode));
    p->next = head;
    q->next = head;

    while(i<n) {
        q = q -> next;
        i++;
    }

    while(q->next != NULL) {
        q = q -> next;
        p = p -> next;
    }

   if(p -> next == head) {
        return head -> next;
   } else {
        p->next = p->next ->next;
   }

   return head;
}


画外音:代码实现小莱在leetcode亲测!!!

总结

1、在链表长度法里,我们在已知链表长度中虽然根据链表长度只需要进行一次扫描操作即可删除指定节点,但这并不是一种理想方式。


2、未知链表长度情况下,使用前后双指针法进行一趟扫描即可删除指定节点。