leetcode
leetcode 1851 ~ 1900
删除链表的中间节点

删除链表的中间节点

难度:

标签:

题目描述

给你一个链表的头节点 head删除 链表的 中间节点 ,并返回修改后的链表的头节点 head

长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于 x 的最大整数。

  • 对于 n = 12345 的情况,中间节点的下标分别是 01122

 

示例 1:

输入:head = [1,3,4,7,1,2,6]
输出:[1,3,4,1,2,6]
解释:
上图表示给出的链表。节点的下标分别标注在每个节点的下方。
由于 n = 7 ,值为 7 的节点 3 是中间节点,用红色标注。
返回结果为移除节点后的新链表。 

示例 2:

输入:head = [1,2,3,4]
输出:[1,2,4]
解释:
上图表示给出的链表。
对于 n = 4 ,值为 3 的节点 2 是中间节点,用红色标注。

示例 3:

输入:head = [2,1]
输出:[2]
解释:
上图表示给出的链表。
对于 n = 2 ,值为 1 的节点 1 是中间节点,用红色标注。
值为 2 的节点 0 是移除节点 1 后剩下的唯一一个节点。

 

提示:

  • 链表中节点的数目在范围 [1, 105]
  • 1 <= Node.val <= 105

代码结果

运行时间: 1308 ms, 内存: 56.8 MB


/*
 * 思路:
 * 1. 将链表转换为流。
 * 2. 找到中间节点的索引。
 * 3. 跳过中间节点并重新组装链表。
 */

import java.util.*;
import java.util.stream.*;

class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; }
}

public class Solution {
    public ListNode deleteMiddle(ListNode head) {
        if (head == null || head.next == null) return null;
        List<ListNode> nodes = new ArrayList<>();
        for (ListNode current = head; current != null; current = current.next) {
            nodes.add(current);
        }
        int middle = nodes.size() / 2;
        nodes.remove(middle); // 删除中间节点
        // 重新组装链表
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (ListNode node : nodes) {
            current.next = node;
            current = node;
        }
        current.next = null; // 防止循环引用
        return dummy.next;
    }
}

解释

方法:

这个题解使用了快慢指针的方法来查找链表的中间节点。快指针`f`每次移动两步,慢指针`s`每次移动一步。通过这种方式,当快指针到达链表末尾时,慢指针正好位于链表的中间位置。为了能够删除中间节点,我们使用一个额外的指针`pre`,始终指向慢指针的前一个节点,这样我们可以轻松地跳过中间节点,从而实现删除操作。如果链表只有一个节点,直接返回`None`。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
在找到中间节点后,为什么可以直接通过`pre.next = pre.next.next`来删除节点,这种操作是否会影响链表的完整性?
在找到中间节点后,可以通过`pre.next = pre.next.next`来删除节点,这是因为`pre`指针指向待删除节点的前一个节点,通过修改`pre`的`next`指向,即可将中间节点从链表中断开,链接到中间节点之后的节点。这种操作不会影响链表的完整性,因为所有节点仍然通过链接相连,只是移除了中间的一个节点。
🦆
快慢指针法在遍历链表时,如果链表长度为偶数,慢指针停止的位置是否确实是中间节点,还是偏向前一个?
当链表长度为偶数时,慢指针`s`停止的位置是两个中间节点中的第一个。这是因为当快指针`f`到达链表末尾时(`f.next`为`None`),慢指针`s`将位于前半部分的最后一个节点。因此,在偶数长度的链表中,若定义中间节点为两个中间节点中的第二个,慢指针实际上停在了偏向前一个。
🦆
题解中提到,如果链表只有一个节点,直接返回`None`。在实际应用中,对于只有两个节点的链表,删除操作的结果是什么?
对于只有两个节点的链表,根据题解中的逻辑,快指针`f`会在第二个节点处停止(因为`f.next.next`为`None`),这时慢指针`s`和`pre`都停在第一个节点。执行`pre.next = pre.next.next`(即`pre.next = s.next`)将删除第二个节点。因此,链表将只剩下第一个节点,即删除中间节点后,返回的结果为只含有第一个节点的链表。
🦆
快指针`f`在遍历时使用了`f.next.next`,这在链表节点个数为奇数和偶数时的行为是否一致?如何确保不会出现访问空指针的错误?
快指针`f`在遍历时使用`f.next.next`确保每次前进两步。在链表节点数为奇数和偶数时,快指针的行为略有不同:对于奇数节点,快指针最终会停在最后一个节点(`f.next`为`None`);对于偶数节点,快指针会停在倒数第二个节点(`f.next.next`为`None`)。为确保不会出现访问空指针错误,代码中应在访问`f.next.next`之前检查`f`和`f.next`是否为`None`。这种检查在题解中已经实现,确保了代码在遍历过程中不会引发空指针异常。

相关问题