本文是【常用算法思路分析系列】的第四篇,总结链表相关的高频题目和解题思路。本文分析如下几个问题:1、环形链表的差值问题;2、只能访问单个结点的删除问题;3、链表的分化;4、打印两个链表的公共部分;5、把链表的每k个结点逆序;6、删除链表中指定结点;7、判断链表是否为回文结构;8、复杂链表的复制;9、判断链表是否有环;10、判断两个无环链表是否相交;11、判断两个有环链表是否相交;12、判断两个链表(状态未定)是否相交。
本系列前三篇导航:
1、环形链表插值问题
有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。
给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。
测试样例:
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
public class InsertValue { /** * @param args */ public static void main(String[] args) { int[] a = {1,3,4,5,7}; int[] b = {1,2,3,4,0}; // insert(a,b,2); System.out.println("dd"); } public static class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public static ListNode insert(int[] A, int[] nxt, int val) { ListNode head = null; if(A == null || A.length == 0){ head = new ListNode(val); head.next = null; }else{ //构造环形链表 ListNode curNode = new ListNode(A[0]); head = curNode; for(int i = 0; i < nxt.length - 1; i++){ ListNode node = new ListNode(A[nxt[i]]); curNode.next = node; curNode = node; } curNode.next = head; ListNode pre = head; curNode = head.next; boolean flag = false; while(curNode != head){ if(val <= curNode.val){ ListNode node = new ListNode(val); node.next = curNode; pre.next = node; flag = true; break; }else{ pre = curNode; curNode = curNode.next; } } if(!flag){//表示没有找到合适的位置,需要插入到头结点的前面(可能是val大于所有值或小于所有节点值) ListNode node = new ListNode(val); node.next = head; pre.next = node; if(val <= head.val){ head = node; } } } return head; } }
我这个没有通过牛客的测试,说是超时,但我自己测试并没有发现有死循环的可能啊。。有知道的可以提示一下~
2、只能访问单个节点的删除
实现一个算法,删除单向链表中间的某个结点,没有给定头结点,你只能访问该结点。
给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true
思路:
将删除节点的下一个结点值赋给要删除的当前结点,然后当前结点指向它下一个结点的下一个结点。即实质是把删除节点的下一个结点值赋给当前节点,然后删除当前节点的下一个结点。这种情况下,最后一个节点是不能删除的。
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public boolean removeNode(ListNode pNode) { if(pNode == null || pNode.next == null) return false; ListNode nextNode = pNode.next; pNode.val = nextNode.val; pNode.next = nextNode.next; return true; }
3、链表的分化
对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。
给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。
测试样例:
{1,4,2,5},3
{1,2,4,5}
思路:
定义一个指向结点值<=val的结点的链表,一个结点值>val的结点的链表,开始从头结点开始遍历,遍历到当前结点的时候,先用next指向当前结点的后面链表,取出当前结点,对当前结点的值进行比较,决定放到哪个链表中。这里要特别注意,对于这种链表分化的操作,我们在遍历链表的时候,一定要把当前结点取出来,将其next置为null,即脱离原来的链表,否则,如果不把当前结点取出next置null,在组拼两个链表后,组拼后的链表中可能存在环!
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public ListNode listDivide(ListNode head, int val) { if(head == null) return null; ListNode minList = null,maxList = null; ListNode minListHead = null,maxListHead = null; ListNode next = null; while(head != null){ next = head.next; head.next = null; if(head.val <= val){//如果当前结点值<=val,将当前结点放入到小于阀值的链表 if(minList == null){ minList = head; minListHead = head; }else{ minList.next = head; minList = head; } }else{//如果当前结点值>val,将当前结点放入到大于阀值的链表 if(maxList == null){ maxList = head; maxListHead = head; }else{ maxList.next = head; maxList = head; } } head = next; } if(minList != null){ minList.next = maxListHead; } return minListHead != null ? minListHead : maxListHead; }
4、打印两个链表的公共值部分
现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。
给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值
测试样例:
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]
思路:
由于链表都是有序的,因此从头结点开始遍历两个链表,如果headA指向的结点值小于headB指向的结点值,headA往后移动一次,如果headA指向的结点值大于headB指向的结点值,headB往后移动一次,如果相等,则可以打印该值,两个链表同时移动。如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public int[] findCommonParts(ListNode headA, ListNode headB) { if(headA == null || headB == null){ return null; } List<Integer> list = new ArrayList<Integer>(); while(headA != null && headB != null){ if(headA.val < headB.val){ headA = headA.next; }else if(headA.val > headB.val){ headB = headB.next; }else{//相等,打印值 list.add(headA.val); headA = headA.next; headB = headB.next; } } int[] res = new int[list.size()]; for(int i = 0; i < list.size(); i++){ res[i] = list.get(i); } return res; }
5、把链表的每k个元素逆序
有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。
给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。
思路一:
利用一个栈进行反转排序,从头开始遍历元素,每遍历一个元素,将其取下(next置为null),放入到栈中,当栈中元素达到k个时,对栈中的元素进行出栈,记录好头部和尾部结点,并链接到上一个反转后的尾部。由于利用了一个栈,因此这种算法时间复杂度为O(N),空间复杂度为O(K)。代码如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public ListNode inverse(ListNode head, int k) { if(head == null || head.next == null || k < 2){ return head; } Stack<ListNode> stack = new Stack<ListNode>(); ListNode next = null; ListNode resHead = null;//反转后的链表的头结点 ListNode header = null, tail = null;//记录每次反转过程中的头结点和尾结点 ListNode lastTail = null;//上一次反转过程中的尾结点 while(head != null){ next = head.next; head.next = null; stack.push(head); if(stack.size() == k){ //先退栈反转这k个元素,找到这k个元素的头和尾部 header = tail = stack.pop(); while(!stack.isEmpty()){ tail.next = stack.pop(); tail = tail.next; } if(resHead == null){//是否是第一次反转前k个元素 resHead = header; lastTail = tail; }else{ lastTail.next = header; lastTail = tail; } } head = next; } if(!stack.isEmpty()){//栈中还有元素,表示最后几个元素没有满足k个数量,保持原有顺序 header = stack.pop(); header.next = null; ListNode temp ; while(!stack.isEmpty()){//头插法,维持原来顺序 temp = stack.pop(); temp.next = header; header = temp; } lastTail.next = header; } return resHead; }
思路二:
不借助栈,每次从链表中截取处k个元素,对这k个元素利用头插法进行反转。时间复杂度为O(N),空间复杂度为O(1),如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public ListNode inverse(ListNode head, int k) { if(head == null || head.next == null || k < 2){ return head; } ListNode next = null; ListNode resHead = null;//反转后的链表的头结点 ListNode header = null, tail = null;//记录每次截取k个元素过程中的头结点和尾结点 ListNode lastTail = null;//上一次反转过程中的尾结点 boolean flag = true; while(flag){ header = null; tail = null; //每次拿出k个元素 for(int i = 0; i < k; i++){ if(head == null){//表示最后一组不满足k个元素 flag = false; break; }else{ next = head.next; head.next = null; if(i == 0){ header = head; tail = head; }else{ tail.next = head; tail = head; } head = next; } } if(flag){//反转这k个元素 ListNode tempListHeader = header; ListNode temp = tempListHeader; tempListHeader = tempListHeader.next; temp.next = null;//摘取出来 while(tempListHeader != null){ next = tempListHeader.next; tempListHeader.next = temp; temp = tempListHeader; tempListHeader = next; } if(resHead == null){ resHead = tail; lastTail = header; }else{ lastTail.next = tail; lastTail = header; } }else{//最后一组不满足k个元素,直接放到反转后的链表后面 lastTail.next = header; } } return resHead; }
6、删除链表中指定的元素
现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。
给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。
测试样例:
{1,2,3,4,3,2,1},2
{1,3,4,3,1}
思路一:
定义first、tail链表结点,用于指向新链表的头结点和尾结点,遍历链表,当前结点值!=val时,将当前结点加入到tail的后面,如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public ListNode clear(ListNode head, int val) { if(head == null) return null; ListNode first = null,tail = null,next = null; while(head != null){ next = head.next; head.next = null; if(head.val != val){ if(first == null){ first = head; tail = head; }else{ tail.next = head; tail = head; } } head = next; } return first; }
思路二:
思路一相当于用了一个新链表,虽然空间复杂度还是为O(1),我们用一种在原链表上进行操作的办法。
对于单链表,要进行删除操作,重要的是记录当前删除结点的前一个结点,因此,我们定义pre、cur结点指针。如下:
public ListNode clear(ListNode head, intnum) { while(head != null) { if(head.val != num) { break; } head = head.next; } ListNode pre = head; ListNode cur = head; while(cur != null) { if(cur.val == num) { pre.next = cur.next; } else{ pre = cur; } cur = cur.next; } return head; }
7、判断链表是否为回文结构
编写一个函数,检查链表是否为回文。回文链表结构如下:1->2->3->2->1为回文结构,1->2->3->1不是回文结构。即回文结构是左右对称的。
思路一:
利用栈结构,第一遍遍历链表,将所有结点入栈;第二遍遍历链表,将当前遍历结点的值依次与栈中弹出的栈顶结点比较,只有全部相同才为回文结构。如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public boolean isPalindrome(ListNode pHead) { if(pHead == null){ return false; } Stack<ListNode> stack = new Stack<ListNode>(); ListNode first = pHead; while(first != null){//链表中全部结点先入栈 stack.push(first); first = first.next; } ListNode temp; while(pHead != null && !stack.isEmpty()){//再遍历一遍链表 temp = stack.pop(); if(temp.val != pHead.val){ return false; }else{ pHead = pHead.next; } } return true; }
时间复杂度为O(N),空间复杂度为O(N)。
思路二:
同样是利用一个栈结构,利用回文的特性,即左右结构对称,定义快、慢两个指针,初始都指向头结点,快指针每次走2步,慢指针每次走1步,把慢指针指向的结点入栈。当快指针到达尾部时,慢指针到达链表中部,如果链表结点数量为奇数,则慢指针指向的中间结点不入栈。此后,慢指针继续遍历后面的元素,每遍历一个结点,和栈中弹出的栈顶结点值进行比较,只有完全相同的时候,才为回文结构。、
思路二比思路一主要的优化在于栈中元素少了一半,为N/2,代码如下:
public boolean isPalindrome(ListNode pHead) { if(pHead == null){ return false; } Stack<ListNode> stack = new Stack<ListNode>(); ListNode fast = pHead; ListNode slow = pHead; while(fast != null){ if(fast.next == null){//表示到快指针达尾部,说明链表结点个数为奇数,此时慢指针到达中部,且此时慢指针指向的结点不需要入栈 slow = slow.next;//链表结点数为奇数时,跳过中间结点 break; } stack.push(slow); slow = slow.next; fast = fast.next.next; } ListNode temp; while(slow != null && !stack.isEmpty()){//后面慢指针和栈顶结点值依次比较 temp = stack.pop(); if(temp.val != slow.val){ return false; }else{ slow = slow.next; } } return true; }
思路三(最优解):
不借助栈结构,同样是使用快慢指针,利用快慢指针,先找到链表中点位置,将中点位置的后半段链表进行反转,得到反转后的链表,对反转后的链表和原来链表的前半段进行依次遍历操作,比较两者的结点值是否相同,只有都相同才是回文结构。记得,不论是否是回文结构,都要把反转后的链表再次反转回来。示意图如下:
代码如下:
public boolean isPalindrome(ListNode pHead) { if(pHead == null){ return false; } ListNode fast = pHead; ListNode slow = pHead; ListNode lastHead = null; while(fast.next != null && fast.next.next != null){//寻找到慢指针指向的中点 slow = slow.next; fast = fast.next.next; } if(fast.next != null){//表示链表节点数量为偶数 lastHead = slow.next; }else{ lastHead = slow; } ListNode head = lastHead;//指向反转后的头结点 lastHead = lastHead.next; ListNode next = null; head.next = null; while(lastHead != null){//反转链表 next = lastHead.next; lastHead.next = head; head = lastHead; lastHead = next; } boolean res = true; ListNode tempList = head; while(tempList != null && pHead != null){ if(tempList.val != pHead.val){ res = false; break; } tempList = tempList.next; pHead = pHead.next; } //再把后面反转的链表反转回来 ListNode head2 = head; head = head.next; head2.next = null; while(head != null){ next = head.next; head.next = head2; head2 = head; head = next; } slow.next = head2; return res; }
8、复杂链表的复制
一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),要求复制一份这种链表。
思路:
首先,第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
第二遍遍历结点,设置新结点的random指针。可以结合图,看一下,每个新结点的random指针,指向的是,它的前面一个结点(父本)的random指向的结点的下一个结点。
第三遍遍历结点,还原原来链表,取出所有的新结点;
示意图如下:
代码如下:
public class RandomListNode { int label; RandomListNode next = null; RandomListNode random = null; RandomListNode(int label) { this.label = label; } } public RandomListNode Clone(RandomListNode pHead){ if(pHead == null){ return null; } RandomListNode list = pHead; //第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置; while(list != null){ RandomListNode node = new RandomListNode(list.label); node.next = list.next; list.next = node; list = list.next.next; } //第二遍遍历结点,设置新结点的random指针 list = pHead; while(list != null){ if(list.random != null){ list.next.random = list.random.next; }else{ list.next.random = null; } list = list.next.next; } list = pHead; RandomListNode newList = null; RandomListNode temp = null; RandomListNode res = null; //第三遍遍历结点,还原原来链表,取出所有的新结点; while(list != null){ temp = list.next; list.next = list.next.next; temp.next = null; list = list.next; if(newList == null){ newList = temp; res = temp; }else{ newList.next = temp; newList = temp; } } return res; }
9、判断链表是否有环
如何判断一个单链表是否有环?有环的话返回进入环的第一个节点的值,无环的话返回-1。如果链表的长度为N,请做到时间复杂度O(N),额外空间复杂度O(1)。
思路一:
有点暴力破解的意思,利用哈希表,遍历链表过程中,在将结点放入到哈希表中前,先去判断哈希表中是否有该结点,如果有表示有环,没有就加入到哈希表中,如果无环,会因为null退出循环。注意,哈希表中方的是结点对象,而不是存放的结点值,因此,只有在放入前发现前面已经放入过相同的对象,则表示这是第一个进入环的地方。代码如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public int chkLoop(ListNode head) { if(head == null){ return -1; } int res = -1; Map<ListNode,Integer> map = new HashMap<ListNode,Integer>(); while(head != null){ if(map.containsKey(head)){ return head.val; } map.put(head, 1); head = head.next; } return res; }
思路一时间复杂度为O(N),空间复杂度为O(N)。
思路二(推荐):
利用快慢指针遍历链表,确定是否有环。快指针走两步,慢指针走一步,如果快指针指向了null,表示无环;如果快指针和慢指针最后指向的同一个结点,表示链表中有环。再来确定第一个进入环的位置,当前面快指针和慢指针指向同一个结点时,快指针重新指向链表头结点,此后,慢指针和快指针同步以一个步伐的速度前进,当再一次相遇时,这个结点就是第一次进入环的结点。
public int chkLoop(ListNode head) { if(head == null || head.next == null){//链表为空或者只有一个结点 return -1; } ListNode fast = head.next.next;//我的判断条件中是fast!=slow,因此这里不能设置fast=slow=head; ListNode slow = head.next; while(fast != null && fast.next != null && fast != slow){//如果无环,则fast或fast.next会指向null,如果有环,某一时刻fast==slow fast = fast.next.next; slow = slow.next; } if(fast == slow){//表示有环 fast = head; while(fast != slow){ fast = fast.next; slow = slow.next; } return fast.val; } return -1; }
思路二时间复杂度为O(N),空间复杂度为O(1)。
10、判断两个无环链表是否相交
现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。
给定两个链表的头结点headA和headB,请返回一个bool值,代表这两个链表是否相交。
思路一:
利用哈希表,首先遍历一遍链表A,将A的所有结点放入到哈希表中,再遍历一遍B,遍历时,判断哈希表中是否有相同的结点,有表示相交;
此时空间复杂度为O(N),不满足要求;
思路二:
如果两个链表相交,后端对齐后,那说明两个链表的最后一个结点相同。因此,这种思路是,分别遍历链表A和B,指针指向两个链表的最后一个结点,如果租后一个结点相同,则表示相交。这种方式,虽然空间复杂度为O(1),但是这种方式不能够知道最初相交的结点在哪里。如下:
public boolean chkIntersect(ListNode headA, ListNode headB) { // write code here ListNode alast=headA,blast=headB; while(alast.next!=null){ alast=alast.next; } while(blast.next!=null){ blast=blast.next; } if(alast==blast) returntrue; else returnfalse; }
思路三:
如果要知道最初相交的结点在哪里,可用如下方法。首先分别遍历一遍链表A和B,得到A和B的长度m和n,如果m>n,A从头开始先遍历m-n步,如果m<n,B先遍历n-m步,此后A、B同时移动,如果遍历过程中A和B的当前结点相同,表示两个链表相交,且当前结点为相交的第一个结点。如下:
public boolean chkIntersect(ListNode headA, ListNode headB) { if(headA == null || headB == null){ return false; } ListNode listA = headA; ListNode listB = headB; int m = 0;//链表A的长度 int n = 0;//链表B的长度 int dst = 0;//两个链表的长度差值 while(listA != null){ m++; listA = listA.next; } while(listB != null){ n++; listB = listB.next; } listA = headA; listB = headB; if(m >= n){ dst = m - n; for(int i = 0; i < dst; i++){ listA = listA.next; } }else{ dst = n - m; for(int i = 0; i < dst; i++){ listB = listB.next; } } while(listA != null && listB != null && listA != listB){ listA = listA.next; listB = listB.next; } if(listA != null && listB != null && listA == listB){ return true; } return false; }
额外空间复杂度为O(1)。
11、有环单链表相交判断
如何判断两个有环单链表是否相交?相交的话返回第一个相交的节点,不想交的话返回空。如果两个链表长度分别为N和M,请做到时间复杂度O(N+M),额外空间复杂度O(1)。
思路:
对于两个有环链表,通过上面介绍的寻找单链表的入环点的方法,拿到两个链表的入环点进行比较:
(1)如果这两个链表的入环结点相同,那说明这两个链表一定相交。此时,是如下这种情况:
A、B第一次相交的结点在共同入环结点的上面,这时,和上面判断两个无环链表是否相交一样,分别计算链表A和B到共同入环结点出的长度,让长度更长的结点先走长度的差值,然后两个链表共同移动,第一次相等的结点即为A和B第一次相交的结点。
(2)如果两个链表的入环结点不相同,此时,又有两种情况,如下:
这种情况下,只需要对链表A从入环结点出开始遍历一下,在再次回到入环结点前,看遍历节点中是否有B的入环结点。此时,A和B第一次相交的结点可以是A的入环结点或者是B的入环结点。
代码实现如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public boolean chkInter(ListNode head1, ListNode head2) { if(head1 == null || head2 == null){ return false; } ListNode loopNode1 = getLoopStartNode(head1);//拿到链表head1的入环结点 ListNode loopNode2 = getLoopStartNode(head2);//拿到链表head2的入环结点 if(loopNode1 == null || loopNode2 == null) return false; if(loopNode1 == loopNode2){//表示两个链表的入环结点相同,则两个链表肯定相交 return true; }else{ ListNode temp = loopNode1.next; while(temp != loopNode1){ if(temp == loopNode2){//表示环中有相交结点 return true; } temp = temp.next; } } return false; } public ListNode getLoopStartNode(ListNode head){ ListNode fast = head.next.next; ListNode slow = head.next; while(fast != null && fast.next != null && fast != slow){ fast = fast.next.next; slow = slow.next; } if(fast == slow){ fast = head; while(fast != slow){ fast = fast.next; slow = slow.next; } return fast; } return null; }
12、判断两个链表(状态未知)是否相交
给定两个单链表的头节点head1和head2,如何判断两个链表是否相交?相交的话返回true,不想交的话返回false。
思路:
这个问题是前面9、10、11问题的综合,因为这里的两个链表状态未知,即不清楚是否有环,或者哪个有环,哪个无环。因此,首先要通过第9节的算法,拿到两个单链表的入环结点loop1和loop2进行分析比较:
(1)一个入环结点不为null,另一个入环结点为null,即一个链表有环,一个无环。这种情况下,两个链表不可能相交;
(2)两个入环结点都为null,此时,问题回到第10节的算法,判断两个无环结点是否相交的问题;
(3)如果入环结点都不为null,问题回到第11节的算法,判断两个有环结点是否相交的问题。
代码如下:
public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) { if(head1 == null || head2 == null){ return false; } ListNode loopNode1 = getLoopStartNode(head1);//拿到链表head1的入环结点 ListNode loopNode2 = getLoopStartNode(head2);//拿到链表head2的入环结点 //如果一个链表有环,一个链表无环,那么这两个链表不可能相交 if((loopNode1 != null && loopNode2 == null) || (loopNode2 != null && loopNode1 == null)){ return false; } //如果两个链表都没有环,则问题回到两个无环链表是否相交的判断上 if(loopNode1 == null && loopNode2 == null){ return chkNoLoopList(head1,head2); } //如果两个入环结点都不为空,即问题回到两个有环链表是否相交的判断上 if(loopNode1 != null && loopNode2 != null){ return chkHasLoopList(loopNode1,loopNode2); } return false; } /** * 判断两个有环链表是否相交 * @param loopNode1 * @param loopNode2 * @return */ public boolean chkHasLoopList(ListNode loopNode1, ListNode loopNode2) { if(loopNode1 == loopNode2){//表示两个链表的入环结点相同,则两个链表肯定相交 return true; }else{ ListNode temp = loopNode1.next; while(temp != loopNode1){ if(temp == loopNode2){//表示环中有相交结点 return true; } temp = temp.next; } } return false; } /** * 判断两个无环链表是否相交 * @param headA * @param headB * @return */ public boolean chkNoLoopList(ListNode headA, ListNode headB) { if(headA == null || headB == null){ return false; } ListNode listA = headA; ListNode listB = headB; int m = 0;//链表A的长度 int n = 0;//链表B的长度 int dst = 0;//两个链表的长度差值 while(listA != null){ m++; listA = listA.next; } while(listB != null){ n++; listB = listB.next; } listA = headA; listB = headB; if(m >= n){ dst = m - n; for(int i = 0; i < dst; i++){ listA = listA.next; } }else{ dst = n - m; for(int i = 0; i < dst; i++){ listB = listB.next; } } while(listA != null && listB != null && listA != listB){ listA = listA.next; listB = listB.next; } if(listA != null && listB != null && listA == listB){ return true; } return false; } /** * 获取链表的入环结点 * @param head * @return */ public ListNode getLoopStartNode(ListNode head){ if(head == null || head.next == null){//链表为null或者只有一个结点,此时没有入环结点 return null; } ListNode fast = head.next.next; ListNode slow = head.next; while(fast != null && fast.next != null && fast != slow){ fast = fast.next.next; slow = slow.next; } if(fast == slow){ fast = head; while(fast != slow){ fast = fast.next; slow = slow.next; } return fast; } return null; }
下一篇将是与二分搜索相关。