题目

标题和出处

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

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

难度

3 级

题目描述

要求

给你一个链表,删除链表的倒数第 链表题目:删除链表的倒数第 N 个结点 - 图1 个结点,然后返回链表的头结点。

示例

示例 1:

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

输入:链表题目:删除链表的倒数第 N 个结点 - 图3
输出:链表题目:删除链表的倒数第 N 个结点 - 图4

示例 2:

输入:链表题目:删除链表的倒数第 N 个结点 - 图5
输出:链表题目:删除链表的倒数第 N 个结点 - 图6

示例 3:

输入:链表题目:删除链表的倒数第 N 个结点 - 图7
输出:链表题目:删除链表的倒数第 N 个结点 - 图8

数据范围

  • 链表中结点的数目为 链表题目:删除链表的倒数第 N 个结点 - 图9
  • 链表题目:删除链表的倒数第 N 个结点 - 图10
  • 链表题目:删除链表的倒数第 N 个结点 - 图11
  • 链表题目:删除链表的倒数第 N 个结点 - 图12

进阶

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

解法一

思路和算法

最直观的做法是,首先遍历链表得到链表的结点数量 链表题目:删除链表的倒数第 N 个结点 - 图13,然后再次遍历链表,找到待删除的结点并将其删除。当链表的结点数量是 链表题目:删除链表的倒数第 N 个结点 - 图14 时,删除倒数第 链表题目:删除链表的倒数第 N 个结点 - 图15 个结点等价于删除正数第 链表题目:删除链表的倒数第 N 个结点 - 图16 个结点。

链表题目:删除链表的倒数第 N 个结点 - 图17 时,待删除的结点为链表的头结点,因此返回 链表题目:删除链表的倒数第 N 个结点 - 图18

链表题目:删除链表的倒数第 N 个结点 - 图19 时,定位到待删除结点的前一个结点 链表题目:删除链表的倒数第 N 个结点 - 图20,然后将结点 链表题目:删除链表的倒数第 N 个结点 - 图21 删除。具体做法如下:

  1. 结点 链表题目:删除链表的倒数第 N 个结点 - 图22 为链表的正数第 链表题目:删除链表的倒数第 N 个结点 - 图23 个结点,因此从 链表题目:删除链表的倒数第 N 个结点 - 图24 开始向后移动 链表题目:删除链表的倒数第 N 个结点 - 图25 次,即可得到结点 链表题目:删除链表的倒数第 N 个结点 - 图26
  2. 删除 链表题目:删除链表的倒数第 N 个结点 - 图27 的后一个结点,可通过改变 链表题目:删除链表的倒数第 N 个结点 - 图28 指针的指向实现,令 链表题目:删除链表的倒数第 N 个结点 - 图29 指向 链表题目:删除链表的倒数第 N 个结点 - 图30 即可。

如果待删除的结点是链表的最后一个结点,上述做法同样适用,在删除结点之后,链表题目:删除链表的倒数第 N 个结点 - 图31 将指向 链表题目:删除链表的倒数第 N 个结点 - 图32

下图为示例 1 的删除结点的过程。此时 链表题目:删除链表的倒数第 N 个结点 - 图33链表题目:删除链表的倒数第 N 个结点 - 图34,待删除的结点是正数第 链表题目:删除链表的倒数第 N 个结点 - 图35 个结点,因此定位到正数第 链表题目:删除链表的倒数第 N 个结点 - 图36 个结点,然后令正数第 链表题目:删除链表的倒数第 N 个结点 - 图37 个结点的 链表题目:删除链表的倒数第 N 个结点 - 图38 指向正数第 链表题目:删除链表的倒数第 N 个结点 - 图39 个结点,完成删除操作。

3_1.png

代码

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. int sz = 0;
  4. ListNode temp = head;
  5. while (temp != null) {
  6. sz++;
  7. temp = temp.next;
  8. }
  9. if (n == sz) {
  10. return head.next;
  11. }
  12. temp = head;
  13. int before = sz - n;
  14. for (int i = 1; i < before; i++) {
  15. temp = temp.next;
  16. }
  17. temp.next = temp.next.next;
  18. return head;
  19. }
  20. }

复杂度分析

  • 时间复杂度:链表题目:删除链表的倒数第 N 个结点 - 图41#card=math&code=O%28%5Ctextit%7Bsz%7D%29&id=x4heF),其中 链表题目:删除链表的倒数第 N 个结点 - 图42 是链表的长度。最多需要遍历链表两次,删除结点的时间为 链表题目:删除链表的倒数第 N 个结点 - 图43#card=math&code=O%281%29&id=DdNre)。
  • 空间复杂度:链表题目:删除链表的倒数第 N 个结点 - 图44#card=math&code=O%281%29&id=OFXvy)。

解法二

思路和算法

上述解法需要首先得到链表的结点数量 链表题目:删除链表的倒数第 N 个结点 - 图45,然后进行删除操作,因此需要两次遍历。其实,链表的结点数量 链表题目:删除链表的倒数第 N 个结点 - 图46 不需要事先知道,一次遍历也可以完成删除操作。

由于待删除的是倒数第 链表题目:删除链表的倒数第 N 个结点 - 图47 个结点,因此可以想到使用两个指针,这两个指针指向的结点在链表中相差 链表题目:删除链表的倒数第 N 个结点 - 图48 个位置。用 链表题目:删除链表的倒数第 N 个结点 - 图49链表题目:删除链表的倒数第 N 个结点 - 图50 分别表示两个指针,其中 链表题目:删除链表的倒数第 N 个结点 - 图51链表题目:删除链表的倒数第 N 个结点 - 图52 的后面 链表题目:删除链表的倒数第 N 个结点 - 图53 个位置。当 链表题目:删除链表的倒数第 N 个结点 - 图54 指向链表的最后一个结点时,链表题目:删除链表的倒数第 N 个结点 - 图55 指向待删除结点的前一个结点。

由于待删除的结点可能是链表的头结点,因此需要创建哑节点 链表题目:删除链表的倒数第 N 个结点 - 图56,使得 链表题目:删除链表的倒数第 N 个结点 - 图57。将两个指针 链表题目:删除链表的倒数第 N 个结点 - 图58链表题目:删除链表的倒数第 N 个结点 - 图59 初始化为都指向 链表题目:删除链表的倒数第 N 个结点 - 图60,然后将 链表题目:删除链表的倒数第 N 个结点 - 图61 向后移动 链表题目:删除链表的倒数第 N 个结点 - 图62 次,即满足 链表题目:删除链表的倒数第 N 个结点 - 图63链表题目:删除链表的倒数第 N 个结点 - 图64 的后面 链表题目:删除链表的倒数第 N 个结点 - 图65 个位置。

链表题目:删除链表的倒数第 N 个结点 - 图66链表题目:删除链表的倒数第 N 个结点 - 图67 满足相差 链表题目:删除链表的倒数第 N 个结点 - 图68 个位置时,同时将两个指针向后移动,直到 链表题目:删除链表的倒数第 N 个结点 - 图69 指向链表的最后一个结点,此时 链表题目:删除链表的倒数第 N 个结点 - 图70 指向待删除结点的前一个结点。将 链表题目:删除链表的倒数第 N 个结点 - 图71 定位到待删除结点的前一个结点之后,令 链表题目:删除链表的倒数第 N 个结点 - 图72 指向 链表题目:删除链表的倒数第 N 个结点 - 图73,即可完成删除操作。

完成删除操作之后,新的头结点为哑节点的下一个结点,因此返回 链表题目:删除链表的倒数第 N 个结点 - 图74

下图为示例 1 的删除结点的过程,图中的灰色结点表示哑节点。此时 链表题目:删除链表的倒数第 N 个结点 - 图75,因此将 链表题目:删除链表的倒数第 N 个结点 - 图76 移动到和 链表题目:删除链表的倒数第 N 个结点 - 图77 相差 链表题目:删除链表的倒数第 N 个结点 - 图78 个位置,然后同时向后移动 链表题目:删除链表的倒数第 N 个结点 - 图79链表题目:删除链表的倒数第 N 个结点 - 图80,直到 链表题目:删除链表的倒数第 N 个结点 - 图81 指向链表的最后一个结点,链表题目:删除链表的倒数第 N 个结点 - 图82 指向待删除结点的前一个结点,删除 链表题目:删除链表的倒数第 N 个结点 - 图83 指向的结点的下一个结点。最后返回哑节点的下一个结点。

3_2.png

代码

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. ListNode dummyHead = new ListNode(0, head);
  4. ListNode temp1 = dummyHead, temp2 = dummyHead;
  5. for (int i = 0; i < n; i++) {
  6. temp2 = temp2.next;
  7. }
  8. while (temp2.next != null) {
  9. temp1 = temp1.next;
  10. temp2 = temp2.next;
  11. }
  12. temp1.next = temp1.next.next;
  13. return dummyHead.next;
  14. }
  15. }

复杂度分析

  • 时间复杂度:链表题目:删除链表的倒数第 N 个结点 - 图85#card=math&code=O%28%5Ctextit%7Bsz%7D%29&id=k9hR4),其中 链表题目:删除链表的倒数第 N 个结点 - 图86 是链表的长度。需要遍历链表一次,删除结点的时间为 链表题目:删除链表的倒数第 N 个结点 - 图87#card=math&code=O%281%29&id=QJkbD)。
  • 空间复杂度:链表题目:删除链表的倒数第 N 个结点 - 图88#card=math&code=O%281%29&id=PAhlt)。