leetcode19. 删除链表的倒数第N个节点

题目

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

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

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

进阶
你能尝试使用一趟扫描实现吗?

力扣官网详解

方法一:两次遍历

思路

将删除倒数的节点转换思路,变为删除从列表开始的节点,即将(L-n+1)的节点删除。L是表长,n已知,因此只要知道L表长,那么问题就可以解决

算法

设定一个哑结点,位于列表的头部,让其指向头节点。哑结点的设置是为了解决一些特殊情况,比如只有一个节点,指定删除这个节点。通过第一次遍历,找出列表长度L。然后设置一个指向哑结点的指针,并将其遍历移动到(L-n)的节点处。然后讲(L-n)这个节点的next指针指向(L-n+2)这个节点,那么(L-n+1)这个要删除的节点就被删除了。

在这里插入图片描述
【图源自力扣官方,侵删】

常规思路代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* sentinel = new ListNode(0);
        sentinel->next = head;
        int length = 0;
        ListNode* ret = head;
        if(head->next == NULL) return NULL;
        while(ret != NULL){
            length += 1;// len = 5
            ret = ret->next;
        }
        length -= n;// len = 3
        ret = sentinel;
        while(length > 0){
            ret = ret->next;// 1 2 3
            length--;//2 1 0
        }
        ret->next = ret->next->next;
        return sentinel->next;
    }
};
  • 时间复杂度:o(L)。由于该算法对列表进行了两次遍历,第一次是计算列表的长度,第二次是找到(L-n)这个节点。操作执行了(2L-n)步,因此为o(L)。
  • 空间复杂度:o(1),只用了常量级的额外空间(只用了一个哨兵节点)。

方法二:一次遍历

算法
上述算法可以优化为只使用一次遍历。可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 n 个结点分开。通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。
在这里插入图片描述

优化后的代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* sentinel = new ListNode(0);
        sentinel->next = head;
        ListNode* first = sentinel, * second = sentinel;
        for(int i = 0; i <= n; i++){
            first = first->next;
        }
        while(first != NULL){
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        return sentinel->next;
    }
};

总结

本质是找到要删除结点的前一个节点,但如果是删除头结点,则不存在前一个节点,所以巧妙的用“哑结点”来解决:
第一种方法是常规思路;
第二种方法则巧妙的利用双指针法。

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页