如何快速找到长度未知单链表的中间节点 -- 快慢指针法

据说是腾讯的一个面试题

问题:已知一个长度未知单链表,如何快速找到中间位置的节点?

  • 长度未知
  • 单向链表

分析问题

目的:找到最中间位置的节点。其实就是一个我们如何去遍历这个单链表的问题

初步上手

单链表的特性大家都知道,只能单向遍历,而且此处还不知道链表的长度。一种方法是:先遍历找到链表长度n,然后我们遍历(n+1)/2次,让指针指向链表一半的位置出的节点,此时就是链表的中间位置(忽略偶数长度,偶数长度的话其实有两个,最中间的两个,n/2就可代表中间位置的了,他的下一个也是中间位置节点)。这种思路的话,时间复杂度来说是O(n)。实现的话也很容易,这里就不多说。这个大家应该都可以想到,最容易的一种方式。

优化分析

既然上一个复杂度是O(n),那我本能的想想能否降到对数阶O(logN)呢?这或许有些难度,或许可以降到O(n/2),虽然从简单考虑来说,O(n)与O(n/2),可以说都是线性阶,但是效率上总归是有差别的,毕竟可以少一半的执行时间。那如何来实现呢?

另一种方法,可以通过快慢指针的方式,也就是说我们如果一开始有两个指针一个mid,一个last,然后都初始化指向链表头:

mid = list->head;
last = list->head;

然后以一个循环执行,mid指向下一个,last以两倍的方式增长,就是说last指向下一个的下一个,这样对应的last指向链表尾时,mid就正好指向中间位置。

while(last != NULL)	//当last指向链表尾的next域即last == NULL结束
{
	mid = mid->next;
	last = last->next->next;
}

这样的话当last停下来,mid就是中间位置的指针,而循环只跑了n/2次,也就是时间复杂度为O(n/2)。

写个函数具体实现一下

前置条件:一个非空链表指针;
后置条件:返回中间节点指针;
假设我们已经定义了Node节点类型,以及链表类型List(一个结构,仅含头指针)

Node * FindMid(List * ls)
{
	//传入空链表时或者仅含一个节点,提前结束
	if(ls->head == NULL || ls->head->next == NULL)
		return NULL;

	Node * mid = ls->head;
	Node * last = ls->head;

	//当这个while循环停下来时,last指向尾节点的next,即NULL,而mid恰好指向我们要的中间节点
	//循环条件检查是否当前的下一个是否为空,即是否last指向了链表尾
	while(last->next != NULL)
		if(last->next->next != NULL)
		{
			mid = mid->next;
			last = last->next->next;
		}else
			//最后一次,当前last指向下一个,即前进至链表尾节点,避免last越界
			last = last->next;	

	return mid;
}

总结

题目不难,关键是如何去找到更优的算法,从而在查找效率上取得进步。这样的算法就时间复杂度考虑其实也还是线性阶,不过应该是基于已知条件下的最优算法了,毕竟总体上较之第一个算法时间节省了一半。

启发

快慢指针法,一快一慢,两个指针,快指针类似于一个渐增的标尺,慢指针即标识标尺的中间位置。每轮循环两指针都渐增,当标尺增长至单链表尾,慢指针其实就标识着链表的中间节点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值