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)
- 空间复杂度:两种方法都只使用常数额外空间
- 快慢指针法更优雅,是推荐解法
相关题目
. Remove Nth Node From End of List (Medium)
. Reorder List (Medium)
. Middle of the Linked List (Easy)