链表题目:删除链表的倒数第 N 个结点

题目

标题和出处

标题:删除链表的倒数第 N 个结点

出处:19. 删除链表的倒数第 N 个结点

难度

3 级

题目描述

要求

给你一个链表,删除链表的倒数第 n \texttt{n} n 个结点,然后返回链表的头结点。

示例

示例 1:

示例 1

输入: head   =   [1,2,3,4,5],   n   =   2 \texttt{head = [1,2,3,4,5], n = 2} head = [1,2,3,4,5], n = 2
输出: [1,2,3,5] \texttt{[1,2,3,5]} [1,2,3,5]

示例 2:

输入: head   =   [1],   n   =   1 \texttt{head = [1], n = 1} head = [1], n = 1
输出: [] \texttt{[]} []

示例 3:

输入: head   =   [1,2],   n   =   1 \texttt{head = [1,2], n = 1} head = [1,2], n = 1
输出: [1] \texttt{[1]} [1]

数据范围

  • 链表中结点的数目为 sz \texttt{sz} sz
  • 1 ≤ sz ≤ 30 \texttt{1} \le \texttt{sz} \le \texttt{30} 1sz30
  • 0 ≤ Node.val ≤ 100 \texttt{0} \le \texttt{Node.val} \le \texttt{100} 0Node.val100
  • 1 ≤ n ≤ sz \texttt{1} \le \texttt{n} \le \texttt{sz} 1nsz

进阶

你能使用一次遍历实现吗?

解法一

思路和算法

最直观的做法是,首先遍历链表得到链表的结点数量 sz \textit{sz} sz,然后再次遍历链表,找到待删除的结点并将其删除。当链表的结点数量是 sz \textit{sz} sz 时,删除倒数第 n n n 个结点等价于删除正数第 sz − n + 1 \textit{sz} - n + 1 szn+1 个结点。

n = sz n = \textit{sz} n=sz 时,待删除的结点为链表的头结点,因此返回 head . next \textit{head}.\textit{next} head.next

n < sz n < \textit{sz} n<sz 时,定位到待删除结点的前一个结点 temp \textit{temp} temp,然后将结点 temp . next \textit{temp}.\textit{next} temp.next 删除。具体做法如下:

  1. 结点 temp \textit{temp} temp 为链表的正数第 sz − n \textit{sz} - n szn 个结点,因此从 head \textit{head} head 开始向后移动 sz − n − 1 \textit{sz} - n - 1 szn1 次,即可得到结点 temp \textit{temp} temp

  2. 删除 temp \textit{temp} temp 的后一个结点,可通过改变 next \textit{next} next 指针的指向实现,令 temp . next \textit{temp}.\textit{next} temp.next 指向 temp . next . next \textit{temp}.\textit{next}.\textit{next} temp.next.next 即可。

如果待删除的结点是链表的最后一个结点,上述做法同样适用,在删除结点之后, temp . next \textit{temp}.\textit{next} temp.next 将指向 null \text{null} null

下图为示例 1 的删除结点的过程。此时 sz = 5 \textit{sz} = 5 sz=5 n = 2 n = 2 n=2,待删除的结点是正数第 4 4 4 个结点,因此定位到正数第 3 3 3 个结点,然后令正数第 3 3 3 个结点的 next \textit{next} next 指向正数第 5 5 5 个结点,完成删除操作。

图 1

代码

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int sz = 0;
        ListNode temp = head;
        while (temp != null) {
            sz++;
            temp = temp.next;
        }
        if (n == sz) {
            return head.next;
        }
        temp = head;
        int before = sz - n;
        for (int i = 1; i < before; i++) {
            temp = temp.next;
        }
        temp.next = temp.next.next;
        return head;
    }
}

复杂度分析

  • 时间复杂度: O ( sz ) O(\textit{sz}) O(sz),其中 sz \textit{sz} sz 是链表的长度。最多需要遍历链表两次,删除结点的时间为 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( 1 ) O(1) O(1)

解法二

思路和算法

上述解法需要首先得到链表的结点数量 sz \textit{sz} sz,然后进行删除操作,因此需要两次遍历。其实,链表的结点数量 sz \textit{sz} sz 不需要事先知道,一次遍历也可以完成删除操作。

由于待删除的是倒数第 n n n 个结点,因此可以想到使用两个指针,这两个指针指向的结点在链表中相差 n n n 个位置。用 temp 1 \textit{temp}_1 temp1 temp 2 \textit{temp}_2 temp2 分别表示两个指针,其中 temp 2 \textit{temp}_2 temp2 temp 1 \textit{temp}_1 temp1 的后面 n n n 个位置。当 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点时, temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点。

由于待删除的结点可能是链表的头结点,因此需要创建哑节点 dummyHead \textit{dummyHead} dummyHead,使得 dummyHead . next = head \textit{dummyHead}.\textit{next} = \textit{head} dummyHead.next=head。将两个指针 temp 1 \textit{temp}_1 temp1 temp 2 \textit{temp}_2 temp2 初始化为都指向 dummyHead \textit{dummyHead} dummyHead,然后将 temp 2 \textit{temp}_2 temp2 向后移动 n n n 次,即满足 temp 2 \textit{temp}_2 temp2 temp 1 \textit{temp}_1 temp1 的后面 n n n 个位置。

temp 1 \textit{temp}_1 temp1 temp 2 \textit{temp}_2 temp2 满足相差 n n n 个位置时,同时将两个指针向后移动,直到 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点,此时 temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点。将 temp 1 \textit{temp}_1 temp1 定位到待删除结点的前一个结点之后,令 temp 1 . next \textit{temp}_1.\textit{next} temp1.next 指向 temp 1 . next . next \textit{temp}_1.\textit{next}.\textit{next} temp1.next.next,即可完成删除操作。

完成删除操作之后,新的头结点为哑节点的下一个结点,因此返回 dummyHead . next \textit{dummyHead}.\textit{next} dummyHead.next

下图为示例 1 的删除结点的过程,图中的灰色结点表示哑节点。此时 n = 2 n = 2 n=2,因此将 temp 2 \textit{temp}_2 temp2 移动到和 temp 1 \textit{temp}_1 temp1 相差 2 2 2 个位置,然后同时向后移动 temp 1 \textit{temp}_1 temp1 temp 2 \textit{temp}_2 temp2,直到 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点, temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点,删除 temp 1 \textit{temp}_1 temp1 指向的结点的下一个结点。最后返回哑节点的下一个结点。

图 2

代码

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0, head);
        ListNode temp1 = dummyHead, temp2 = dummyHead;
        for (int i = 0; i < n; i++) {
            temp2 = temp2.next;
        }
        while (temp2.next != null) {
            temp1 = temp1.next;
            temp2 = temp2.next;
        }
        temp1.next = temp1.next.next;
        return dummyHead.next;
    }
}

复杂度分析

  • 时间复杂度: O ( sz ) O(\textit{sz}) O(sz),其中 sz \textit{sz} sz 是链表的长度。需要遍历链表一次,删除结点的时间为 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( 1 ) O(1) O(1)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值