判断一个单链表是否有环,并找出入口


写在前面

判断一个链表是否有环比较容易,但是要找出环的入口并不容易。

单链表环相关的考题很多,比如:

  • 给一个单链表,判断其中是否有环的存在;
  • 如果存在环,找出环的入口点;
  • 如果存在环,求出环上节点的个数;
  • 如果存在环,求出链表的长度;

单链表是否有环

思路

对于这个问题我们可以采用“快慢指针”的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,然后在每一步操作中slow向前走一步即:slow = slow->next,而fast每一步向前两步即:fast = fast->next->next。由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。当两个指针都进入环之后,经过一定步的操作之后,二者一定能够在环上相遇,并且此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇。证明可以看下图:

image.png

代码

func IsRing(list *Node) bool {
	if list == nil {
		return false
	}

	p1 := list.next
	p2 := list.next
	if p2 == nil {
		return nil
	}
	p2 = p2.next
	for p1 != nil || p2 != nil {
		if p1 == p2 {
			return true
		}
		p1 = p1.next
		p2 = p2.next
		if p2 == nil {
			return false
		}
		p2 = p2.next
	}

	return false
}

找环的入口

数学证明:从链表起点head开始到入口点的距离a,与从slow和fast的相遇点到入口点的距离相等。(可以自己证明)

代码

func MeetNode(list *Node) *Node {
	if list == nil {
		return nil
	}

	p1 := list.next
	p2 := list.next
	if p2 == nil {
		return nil
	}
	p2 = p2.next
	var meetNode *Node
	for p1 != nil || p2 != nil {
		if p1 == p2 {
			meetNode = p1
			break
		}
		p1 = p1.next
		p2 = p2.next
		if p2 == nil {
			return nil
		}
		p2 = p2.next
	}

	if meetNode == nil {
		return nil
	}

	// 再分别从头和meetNode开始走,如果相遇则是入口点
	p1 = list
	p2 = meetNode
	for p1 != p2 {
		p1 = p1.next
		p2 = p2.next
	}

	return p1
}

统计环的长度

思路

找到环的入口,再遍历一遍环则可统计环的长度

代码

func MeetNode(list *Node) int {
	if list == nil {
		return 0
	}

	p1 := list.next
	p2 := list.next
	if p2 == nil {
		return 0
	}
	p2 = p2.next
	var meetNode *Node
	for p1 != nil || p2 != nil {
		if p1 == p2 {
			meetNode = p1
			break
		}
		p1 = p1.next
		p2 = p2.next
		if p2 == nil {
			return 0
		}
		p2 = p2.next
	}

	if meetNode == nil {
		return 0
	}

	// 再分别从头和meetNode开始走,如果相遇则是入口点
	p1 = list
	p2 = meetNode
	for p1 != p2 {
		p1 = p1.next
		p2 = p2.next
	}

	// 再遍历一次环
	p := p1.next
	count := 1
	for p != p1 {
		p = p.next
		count++
	}

	return count
}

求链表长度

思路

找到环入口和已知环长度,则链表长度=环外长度+环长度

代码

func MeetNode(list *Node) int {
	if list == nil {
		return 0
	}

	p1 := list.next
	p2 := list.next
	if p2 == nil {
		return 0
	}
	p2 = p2.next
	var meetNode *Node
	for p1 != nil || p2 != nil {
		if p1 == p2 {
			meetNode = p1
			break
		}
		p1 = p1.next
		p2 = p2.next
		if p2 == nil {
			return 0
		}
		p2 = p2.next
	}

	if meetNode == nil {
		return 0
	}

	// 再分别从头和meetNode开始走,如果相遇则是入口点
	p1 = list
	p2 = meetNode
	for p1 != p2 {
		p1 = p1.next
		p2 = p2.next
	}

	// 再遍历一次环
	p := p1.next
	count := 1
	for p != p1 {
		p = p.next
		count++
	}

	// 求头到环入口p1长度
	p = list
	count1 := 0
	for p != p1 {
		count1++
		p = p.next
	}

	return count + count1
}

参考

[1]判断单链表是否有环并找到环的入口等问题


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录