一、题目描述
给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
- 给定链表的结点数介于
1
和100
之间。
二、思路分析
先从最简单的思路说起,想要知道中间节点,首先要知道链表长度信息。
但是从题目信息,我们只得知这是一个单链表,并不知道它的长度信息。
这时候就可以考虑先遍历一遍链表的节点,统计一下链表的长度,然后再求出中位数,这时候就可以获取中间节点了。
代码实现
class Solution {
public ListNode middleNode(ListNode head) {
// 遍历获取链表长度
ListNode temp = head;
int n = 0;
while (temp != null) {
++n;
temp = temp.next;
}
// 获取中间节点的位置
int middle = (n >> 1) + 1;
// 移动到中间节点
while (middle > 1) {
head = head.next;
--middle;
}
return head;
}
}
从代码实现中,我们可以看出空间复杂度为O(1)
,时间复杂度为O(n)
那么是不是还有更加优雅的方法呢?
小伙伴们,我们先来做个小学算术题:在一条笔直笔直的小路上,大白兔和小白兔赛跑,已知大白兔的速度是小白兔的两倍,小白兔从小路中点起跑,大白兔从小路起点起跑,它们两个能否在小路终点相遇呢?
答案是肯定会相遇的,这个都能想明白。
现在小白兔和大白兔要折返回来,请问一下,大白兔跑回起点的时候,小白兔跑到了什么位置呢?没错,就是中点,也就是这题的关键解法~~~
创建快慢指针,当快指针到达终点的时候,慢指针正好到达起点位置。而且快慢指针只需要遍历一边链表,相对于上面的计算长度的解法来说少了第二次查找中间位置的操作。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode bigRabbit = head;
ListNode smallRabbit = head;
// 这一步很重要,一定要判断清楚,哪一个节点不能为空
while (bigRabbit != null && bigRabbit.next != null) {
smallRabbit = smallRabbit.next;
bigRabbit = bigRabbit.next.next;
}
return smallRabbit;
}
}
从代码实现中,我们可以看出空间复杂度为O(1)
,时间复杂度为O(n)
总结
关于链表的问题,基本上就是遍历链表解题的操作,如果遇到需要遍历多次的情况,可以优先考虑是否能用多个指针,每个指针代表一次遍历,从而减少遍历的次数,提高查询效率。