Medium

题目描述

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

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

  • 对于 n = 1、2、3、4、5,中间节点的下标分别是 0、1、1、2、2

示例 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 是中间节点,用红色标记。
删除节点 1 后,只剩下值为 2 的节点 0。

约束条件:

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

解题思路

这道题要求删除链表的中间节点,主要有两种思路:

方法一:两次遍历 第一次遍历计算链表长度 n,然后第二次遍历到第 n/2 个节点的前一个节点,执行删除操作。这种方法简单直观,但需要遍历两次。

方法二:快慢指针(推荐) 使用快慢指针的经典技巧。快指针每次移动两步,慢指针每次移动一步。当快指针到达链表末尾时,慢指针刚好指向中间节点。

为了删除中间节点,我们需要找到中间节点的前一个节点。可以让慢指针指向中间节点的前一个位置,或者记录前一个节点。

特殊情况处理:

  • 如果链表只有一个节点,删除后返回 null
  • 如果链表有两个节点,删除第二个节点

快慢指针方法只需要一次遍历,时间复杂度更优,空间复杂度也是常数级别,是最优解法。

核心思想是利用快慢指针找到中间节点的前驱,然后进行删除操作。

代码实现

class Solution {
public:
    ListNode* deleteMiddle(ListNode* head) {
        // 特殊情况:只有一个节点
        if (!head->next) {
            return nullptr;
        }
        
        // 快慢指针找中间节点的前一个节点
        ListNode* slow = head;
        ListNode* fast = head->next->next;  // fast提前两步,这样slow会停在中间节点的前一个
        
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        
        // 删除中间节点
        slow->next = slow->next->next;
        
        return head;
    }
};
class Solution:
    def deleteMiddle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 特殊情况:只有一个节点
        if not head.next:
            return None
        
        # 快慢指针找中间节点的前一个节点
        slow = head
        fast = head.next.next  # fast提前两步,这样slow会停在中间节点的前一个
        
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        
        # 删除中间节点
        slow.next = slow.next.next
        
        return head
public class Solution {
    public ListNode DeleteMiddle(ListNode head) {
        // 特殊情况:只有一个节点
        if (head.next == null) {
            return null;
        }
        
        // 快慢指针找中间节点的前一个节点
        ListNode slow = head;
        ListNode fast = head.next.next;  // fast提前两步,这样slow会停在中间节点的前一个
        
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        
        // 删除中间节点
        slow.next = slow.next.next;
        
        return head;
    }
}
var deleteMiddle = function(head) {
    // 特殊情况:只有一个节点
    if (!head.next) {
        return null;
    }
    
    // 快慢指针找中间节点的前一个节点
    let slow = head;
    let fast = head.next.next;  // fast提前两步,这样slow会停在中间节点的前一个
    
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    
    // 删除中间节点
    slow.next = slow.next.next;
    
    return head;
};

复杂度分析

复杂度类型快慢指针法两次遍历法
时间复杂度O(n)O(n)
空间复杂度O(1)O(1)

说明:

  • 时间复杂度:快慢指针法只需要一次遍历,两次遍历法需要两次遍历,但都是 O(n)
  • 空间复杂度:两种方法都只使用常数额外空间
  • 快慢指针法更优雅,是推荐解法

相关题目