本文主要用Java实现关于单链表有环的5个问题:
1. 判断一个单链表是否存在环?
2. 若存在环,找到环的入口位置?
3. 进一步,计算换上的节点数?
4. 进一步,计算环外的长度、链表的长度?
5. 环上对面的结点如何求?
解决每个问题的方法:
1. 利用快、慢指针从表头开始往后next,慢指针去追击快指针,若遍历完返回null,就没有环;若慢指针追上快指针相遇,就有环。
2. 经过数学推导,有这么个规律:从相遇点位置和从表头位置同时往后next,会在环的入口处相遇。
3. 基于以上,可以有2种方法:
(1)从环的入口处开始,在环上转一圈,计数。
(2)从相遇点位置同时开始,快慢指针继续转,直到下次二者相遇,二者走过步数差值(或者慢指针走过的步数)就是环的长度。当然,上面的理论是建立在 快指针一次走2步,慢指针一次走一步。
4. 基于以上,知道了入口的位置,从表头开始往入口走,计数。链表长度=环外长度+环长度。
5. 基于这个规律:在环内,快慢指针同时出发,快指针回到原点,慢指针走到距离原点最远处。
具体详细的数学推导参见
Java 代码实现如下:
public class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
public int getVal() {
return val;
}
public void setVal(int val) {
this.val = val;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
'}';
}
}
----------
public class BooleanCircular {
private static ListNode pos;
private static ListNode joint;
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
ListNode listNode4 = new ListNode(4);
ListNode listNode5 = new ListNode(5);
ListNode listNode6 = new ListNode(6);
ListNode listNode7 = new ListNode(7);
ListNode listNode8 = new ListNode(8);
ListNode listNode9 = new ListNode(9);
listNode1.setNext(listNode2);
listNode2.setNext(listNode3);
listNode3.setNext(listNode4);
listNode4.setNext(listNode5);
listNode5.setNext(listNode6);
listNode6.setNext(listNode7);
listNode7.setNext(listNode8);
listNode8.setNext(listNode9);
listNode9.setNext(listNode4);
System.out.println(hasCycle(listNode1));
System.out.println(FindJoint(listNode1));
System.out.println(CircluarCount2(listNode1));
calculateLength(listNode1);
System.out.println(oppositeListNode(listNode4));
}
//判断单链表是否有环
public static boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (slow != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
return true;
}
}
return false;
}
//找到环的入口
public static ListNode FindJoint(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (slow != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
break;
}
}
if (slow == null || fast.next == null) {
System.out.println("没有环");
return null;
}
//记录相遇位置
pos = slow;
ListNode node = slow;
ListNode start = head;
while (node != start) {
node = node.next;
start = start.next;
}
//记录环入口位置
joint = start;
return start;
}
//计算环上的节点数,两种方法
public static int CircluarCount1(ListNode head) {
int count = 1;
//joint是入口位置
ListNode node = joint.next;
while (node != joint) {
node = node.next;
count++;
}
return count;
}
public static int CircluarCount2(ListNode head) {
int count = 1;
//pos是相遇位置
ListNode fast = pos.next.next;
ListNode slow = pos.next;
while (fast != slow) {
fast = fast.next.next;
slow = slow.next;
count++;
}
return count;
}
//计算头结点到环入口的长度、链表的长度
public static void calculateLength(ListNode head) {
int count = 0;
//joint是入口位置
for (ListNode node = head; node != joint; node = node.next, count++) ;
System.out.println("头结点到入口的长度:" + count);
int listCount = count+CircluarCount1(head);
System.out.println("链表的长度:" + listCount);
}
//求环上对面的结点,这里以 listNode4 对面结点 listNode7 来验证
public static ListNode oppositeListNode(ListNode listNode){
ListNode slow = listNode;
ListNode fast = listNode;
do{
slow=slow.next;
fast=fast.next.next;
}while(fast!=listNode);
return slow;
}
}
run结果:
true
ListNode{val=4}
6
头结点到入口的长度:3
链表的长度:9
ListNode{val=7}
本文结束,谢谢浏览!