LeetCode 19. Remove Nth Node From End of List(C++)

题目描述

给定一个单向链表,要求删除从结尾数第n个结点,并返回修改后的表头。
链表结点的定义如下:

struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};

样例

给定数组单向链表 1->2->3->4->5 ,以及 n = 2 ,修改后的链表为 1->2->3->5。

Note
  1. n总是合法的。
  2. 尝试使用一次遍历完成本题。

思路及算法

算法1 保护结点法
  1. 在头结点之前添加保护结点。
  2. 设置两个指针firstsecond,均指向保护结点。
  3. first指针先向后移动n+1个结点。
  4. 然后firstsecond指针同时向后移动,直到first指针指向空,此时second结点指向的下一个结点需要删除。

解释:
始终保持两个指针之间间隔n个结点,在first到达终点时,second的下一个结点就是从结尾数第n个结点。
这样做可以回避掉如果链表只有一个结点,需要单独处理的问题。在处理有对头结点进行操作的问题时,可以创建虚拟结点来刚方便地处理。
还要注意的是,删除一个结点时,我们需要这个待删除结点的前驱结点,所以要使second指向待删除结点的前驱,而不是指向待删除结点本身。

时间复杂度
O(L)

空间复杂度
O(1)

C++ 代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* ext_head = new ListNode(0);
        ext_head -> next = head;

        ListNode* first = ext_head;
        ListNode* second = ext_head;

        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 ext_head -> next;
    }
};

作者:wzc1995
链接:https://www.acwing.com/solution/LeetCode/content/67/
来源:AcWing

算法2 无保护指针的双指针法
  1. 设置firstsecond两个工作指针,均指向头结点。
  2. first指针向后移动n个结点。
  3. second没有指向空,则firstsecond均向后移动一个结点,并设置pre指针,指向头结点。
  4. prefirstsecond指针均向后移动,直到first指向空,此时second即为被删除的结点,通过pre指针将其删除。

解释:
两个工作指针中间间隔n-1个结点,当second指向空时,first指向的就是待删除结点。为了删除first所指向的结点,需要再额外建立一个pre指针指向first的前驱。
需要注意的是,在first、second同时向后移动前,要设置出pre指针,但是此时first就是头指针,是没有前驱的。所以需要在工作指针同时向后移动时,先移动一步,使得first所指结点有前驱。还要注意如果second向后移动n个结点后,已经指向空,则要删除的是头结点,此时first不存在前驱。

时间复杂度
O(L)

空间复杂度
O(1)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        
        ListNode* first = head;//前进n次的工作指针
        ListNode* second = head;//当前指针
        if(!head) return NULL; //长度为0
        else{
            int num = 0;
            for(int i = 0;i<n;i++){
                first = first->next;
            }
            
            //一共有三个指针,但对前驱指针的第一次操作和之后的操作不一样
            if(!p){
                //如果n刚好是链表的长度
                return head->next;
            }
            
            else{
                ListNode* pre = head;
                second = second->next;
                first = first->next;
                while(first){
                    pre = pre->next;
                    second = second->next;
                    first = first->next;
                }
                
                pre->next = second->next;
                return head;
            }
            
        }
    }
};
算法三 对算法二的改进

解释:
算法二的弊端在于firstsecond要在循环外做一次移动。如果把循环的条件改成first->next不为空,则可以省去pre指针的构建。
值得注意的是,链表长度为1的情况是链表长度等于n的子集,进而保证了first不为null。

时间复杂度
O(L)

空间复杂度
O(1)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {            
        if (!head) {
            return nullptr;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        for (int i = 0; i < n; ++i) {
            fast = fast->next;
        }
        if (!fast) {
            auto temp = head->next;
            delete head;
            return temp;
        }
        while (fast->next) {
            fast = fast->next;
            slow = slow->next;
        }
        auto temp = slow->next;
        slow->next = slow->next->next;
        delete temp;
        return head;
    }
};

总结

保护指针法应该说是比较套路的方法,比之后两种方法,能省去一些边界条件的判定。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值