算法67-链表相交问题

先看探索如何求一个链表存在环以及找出环的第一个节点问题

给出一个链表如下

用代码判断是否存在环,并找到环的第一个节点

方法1:
在这里插入图片描述
1 快慢指针,从头结点触发,快指针每次走两步,慢指针每次走一步

2若两个指针相遇,说明存在环,若不会相遇,最终走到nil’,则说明不存在环

3,若相遇,将快指针退回头节点,快指针也每次走一步,慢指针保持不变向前推进,快慢指针再次相遇的点就是环的第一个点

方法2:set方法,遍历一个链表麻将棋节点放进set,若放入时返回以存在,则说明有环,此方法也可以解决两个环是否相交的问题

再来探讨第二个问题。假如有两个链表,如何判断是否相交?

首先需要清楚的一点,两个链表相交后,从交点起,两个链表将共用一个链表,因为每个节点只有一下next指针,一个next指针就意味着不会再分叉

其次,我们来分析,通过上面单链表求环的第一个节点的函数,我们获得了两个链表的环的第一个节点loop1和loop2,那么有四种可能的情况

1 loop1和loop2都为空,这种情况下,可能会出现详见
2,loop1为空,loop2不为空
3,loop1不为空,loop2为空,2和3这两种情况不会出现相交,因为两个链表的tail都不是同一个节点
4,都不为空,可能相交,又可以细分为三种情况
4.1 都有环,但是不相交
4.2 共同拥有环,也就是交点是环的第一个节点或者之前
4.3 焦点在环上
如下:

在这里插入图片描述
4.1
在这里插入图片描述
4.2
在这里插入图片描述
4.3

代码:

函数1 确定链表是否有环,返回环环的第一个节点,没有则返回nil

方法:快慢指针,两个指针第一次相交后,快指针回到头结点,变为每次移动一步,快慢指针再次相遇的节点就是所求的节点

func getloopnode(head *LinkedNode) *LinkedNode{
	//快慢指针一定要先判断的基础,节点大于2个
	if head ==nil || head.next == nil || head.next.next == nil{//只有一个节点或两个节点时,返回头结点
		return nil
	}
	fast:=head
	slow:=head
	//移动款慢指针直到两个指针相交
	for  fast.next != nil && fast.next.next != nil {
		slow=slow.next
		fast=fast.next.next
		// fmt.Println(fast)
		//相交,快指针退回head,快指针每次移动一步,直到快慢指针再次相遇,返回相遇的节点
		if fast == slow{
			fast=head
			for fast.next != nil && slow.next != nil{
				fast=fast.next
				slow=slow.next
				if fast==slow{
					return fast
				}
			}
		}
	}
	return nil
}

函数2 两个链表都没环,求交点

方法, 先判断tail节点是否是同一个地址,是则说明相交,否则说明必定不相交,确定相交,然后从比较短的链表的头结点位置开始,两个链表以同样的位置开始遍历,知道遇到交点

//情况1  都无环
func noloop(head1,head2 *LinkedNode) *LinkedNode{
	tmp1:=head1
	tmp2:=head2
	n1,n2:=0,0
	for tmp1 !=nil{
		tmp1=tmp1.next
		n1++
	}
	for tmp2 !=nil{
		tmp2=tmp2.next
		n2++
	}
	if tmp1 != tmp2{
		return nil
	}
	fmt.Println(n1,n2)
	n:=int(math.Abs(float64(n1)-float64(n2)))
	var first *LinkedNode
	var second *LinkedNode

	if n1>n2{
		first=head1
		second=head2
	}else{
		first=head2
		second=head1
	}
	for n != 0{
		first=first.next
		n--
	}
	for first != second{
		first=first.next
		second=second.next

	}
	return first
}

函数3,都有环,对应上面情况4,得到两个链表的环的第一个节点之后,判断两个节点是否是同一个,是同一个就调用函数2,得到交点;不是同一个,则交点在环上或者不相交,此时令一个loop继续向前,走完一圈没有碰到另一个节点则说明不相交,否则返回交点

func bothloop(loop1,loop2,head1,head2 *LinkedNode)*LinkedNode{
	var tmp *LinkedNode
	//若两个链表的存在环,且环的第一个节点相等,要么所求的点就是loop1,要么在loop1之上
	//此时可以将loop1,loop2作为ail,去求无环部分时候有交点
	//实际上解决了4.1这个分支
	if loop1==loop2{
		tmp=noloop(head1,head2,loop1,loop2)
		if tmp== nil{//无交点返回loop1,说明咯op就是所要求的点
			return loop1
		}else{//有交点返回交点
			return tmp
		}
		//loop1 !=loop2 要么交点是环上的某一点,没要么就是不相交
		//判断交点是否是环上的某一点:loop1继续往前推进,走完环如果都没遇到loop2说明不想交,否则就返回相遇的点既是交点
	}else{
		loop:=loop1
		for loop != loop2{
			loop=loop.next
			if loop==loop1{//若loop循环一圈,未曾遇见loop2,说明不想交
				return nil
			}
		}
		return loop//loop1,loop2相遇,返回这个节点
	}

	return nil
}

整体代码:

package main

import (
	"fmt"
	"math"
	// "sort"
	// "time"
)

//链表本身,包含长度,头结点和尾节点,对于一个双向链表,
//我们只关注它的haea和tail,head和tail是一个个具体的节点(这里我将链表当做单向链表来用)
type LinkedList struct {
	len  int
	head *LinkedNode
	tail *LinkedNode
}
//链表中的一个节点的结构,包含节点的数据,前向指针,后向指针,指向的也是一个节点本身
type LinkedNode struct {
	data int
	next *LinkedNode
	prev  *LinkedNode
}
// type Data struct{
// 	key string
// 	data int
// }
//所有的操作都基于指针
func NewLinkedList() *LinkedList {
	return &LinkedList{
		len:  0,
		head: nil,
		tail: nil,
	}
}
//追加一个节点,链表没有头结点就把自己设为链表的头结点,头尾指针都指向自己
//有头结点就把自己放在最后,1先把自己的prev指向原来的tail,2把原来的tail的next指向自己,3把链表的tail设为自己
func (ll *LinkedList)Add(node *LinkedNode) {
	// node := &LinkedNode{
	// 	data: data,
	// }
	// fmt.Printf("hhhhhhead%v\n", ll.head)

	if ll.head==nil{
		ll.tail=node

		ll.head=node
	}else{
		// fmt.Printf("tttttail%v\n", ll.tail)
		node.prev=ll.tail

		ll.tail.next=node
		ll.tail=node
		
	}
	
	ll.len++//记得更新长度
}


func getloopnode(head *LinkedNode) *LinkedNode{
	//快慢指针一定要先判断的基础,节点大于2个
	if head ==nil || head.next == nil || head.next.next == nil{//只有一个节点或两个节点时,返回头结点
		return nil
	}
	fast:=head
	slow:=head
	//移动款慢指针直到两个指针相交
	for  fast.next != nil && fast.next.next != nil {
		slow=slow.next
		fast=fast.next.next
		// fmt.Println(fast)
		//相交,快指针退回head,快指针每次移动一步,直到快慢指针再次相遇,返回相遇的节点
		if fast == slow{
			fast=head
			for fast.next != nil && slow.next != nil{
				fast=fast.next
				slow=slow.next
				if fast==slow{
					return fast
				}
			}
		}
	}
	return nil
}
//情况1  都无环
//传参加上tail节点,func可以复用,可以指定从哪个节点(tail)向上检查交点
func noloop(head1,head2,tail1,tail2 *LinkedNode) *LinkedNode{
	tmp1:=head1
	tmp2:=head2
	n1,n2:=1,1
	for tmp1 !=tail1{
		tmp1=tmp1.next
		n1++
	}
	for tmp2 !=tail2{
		tmp2=tmp2.next
		n2++
	}
	if tmp1 != tmp2{
		return nil
	}
	fmt.Println(n1,n2)
	n:=int(math.Abs(float64(n1)-float64(n2)))
	var first *LinkedNode
	var second *LinkedNode

	if n1>n2{
		first=head1
		second=head2
	}else{
		first=head2
		second=head1
	}
	for n != 0{
		first=first.next
		n--
	}
	for first != second{
		first=first.next
		second=second.next

	}
	return first
}

func bothloop(loop1,loop2,head1,head2 *LinkedNode)*LinkedNode{
	var tmp *LinkedNode
	//若两个链表的存在环,且环的第一个节点相等,要么所求的点就是loop1,要么在loop1之上
	//此时可以将loop1,loop2作为ail,去求无环部分时候有交点
	//实际上解决了4.1这个分支
	if loop1==loop2{
		tmp=noloop(head1,head2,loop1,loop2)
		if tmp== nil{//无交点返回loop1,说明咯op就是所要求的点
			return loop1
		}else{//有交点返回交点
			return tmp
		}
		//loop1 !=loop2 要么交点是环上的某一点,没要么就是不相交
		//判断交点是否是环上的某一点:loop1继续往前推进,走完环如果都没遇到loop2说明不想交,否则就返回相遇的点既是交点
	}else{
		loop:=loop1
		for loop != loop2{
			loop=loop.next
			if loop==loop1{//若loop循环一圈,未曾遇见loop2,说明不想交
				return nil
			}
		}
		return loop//loop1,loop2相遇,返回这个节点
	}

	return nil
}
//先通过求环的func得到两个链表是否有环,根据结果分4种情况
//1,两个链表都无环,有可能出现相交的情况,找到两个链表的tail节点,如果是同一个指针则说明相交,否则不相交,
//2,一个有环,另一个无环
//3 同上,2和3不可能出现相交,因为两个链表的尾端都不是同一个节点,
//4,都有环,有可能相交

func FindfirstIntersectNode(head1,head2 *LinkedNode) *LinkedNode{
	if head1==nil && head2 ==nil{
		return nil
	}
	if head1.next==nil && head2.next==nil {
		if head1==head2{
			return head1

		}else{
			return nil
		} 
	}
	loop1:=getloopnode(head1)
	loop2:=getloopnode(head2)
	var internode *LinkedNode
	//情况1 都无环的情况,传入头结点和尾节点,去找交点
	if loop1 ==nil && loop2==nil{
		tail1:=gettail(head1)
		tail2:=gettail(head2)
		internode=noloop(head1,head2,tail1,tail2)
	}
	//情况2,2 一个有环  一个无环  都不可能相交  返回nil
	if (loop1 ==nil && loop2 != nil) || (loop2 ==nil && loop1 != nil) {
		return nil
	}
	//情况4,都有环
	if loop1 !=nil && loop2 != nil{
		internode=bothloop(loop1,loop2,head1,head2)
	}
	return internode
	}


func gettail(head *LinkedNode)*LinkedNode{
	for head!=nil{
		if head.next==nil{
			return head
		}else{
			head=head.next

		}
		
	}
	return head

}
func main(){
	var head LinkedNode
	head.data=18

	var l1 LinkedNode
	l1.data=5

	var l2 LinkedNode
	l2.data=20

    var l3 LinkedNode
	l3.data=4

    var l4 LinkedNode
    l4.data=3

	var l5 LinkedNode
    l5.data=8

	var l6 LinkedNode
    l6.data=12

	var l7 LinkedNode
	l7.data=15
	
	var head1 LinkedNode
	head1.data=9

	var ll2 LinkedNode
	ll2.data=22

	list := NewLinkedList()
	list.Add(&head)
	list.Add(&l1)
	list.Add(&l2)
	list.Add(&l3)
	list.Add(&l4)
	list.Add(&l5)
	list.Add(&l6)
	list.Add(&l7)


	list1 := NewLinkedList()
	list1.Add(&head1)
	list1.Add(&ll2)
	list1.Add(&l2)
	list1.Add(&l3)
	list1.Add(&l4)
	list1.Add(&l5)
	list1.Add(&l6)
	list1.Add(&l7)
	// l7.next=&l4
	// fmt.Println(getloopnode(&head))
	fmt.Println(noloop(&head,&head1,&l7,&l7))
	// fmt.Println(gettail(&head1))
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值