如何确定链表中有环
哈希
判断是否有环,最容易的方法是使用Hash,遍历的时候将元素放入到map中,如果有环一定会发生碰撞。发生碰撞的位置也就是入口的位置
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
快慢指针
为什么两个快慢指针一定会相遇
确定是否有环,最有效的方法就是双指针,一个快指针(一次走两步),一个慢指针(一次走一步)。如果快的能到达表尾就不会有环,否则如果存在环,则慢指针一定会在某个位置与快指针相遇。这就像在操场长跑,一个人快一个人慢,只要时间够,快的一定能在某个时候再次追上慢的人(也就是所谓的套圈)。 这里很多人可能会有疑问,因为两者每次走的距离不一样,会不会快的人在追上慢的时候跳过去了导致两者不会相遇呢?
不会!如下图所示,当fast快要追上slow的时候,fast一定距离slow还有一个空格,或者两个空格,不会有其他情况。
- 假如有一个空格,如上图情况1所示,fast和slow下一步都到了3号位置,因此就相遇了。
- 假如有两个空格,如上图情况2所示,fast下一步到达3,而slow下一步到达4,这就变成了情况1了,因此只要有环,一定会相遇。
使用双指针思想寻找是否存在环的方法:
public boolean hasCycle(ListNode head) {
if(head==null || head.next==null){
return false;
}
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)
return true;
}
return false;
}
确定环的入口
当双指针相遇时,将 fast 指针重新放到 head,slow 位置保持不变,fast 和 slow 以相同的速度移动后再次相遇的位置便是环的入口
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null) {
return null;
}
ListNode slow = head, fast = head;
while(fast != null) {
slow = slow.next;
if(fast.next != null) {
fast = fast.next.next;
}else {
return null;
}
if(fast == slow) {
ListNode prt = head;
while(prt != slow) {
prt = prt.next;
slow = slow.next;
}
return prt;
}
}
return null;
}
}
双向链表
基本概念
双向链表节点:
class DoubleNode {
public int data; //数据域
public DoubleNode next; //指向下一个结点
public DoubleNode prev; // 指向上一个节点
public DoubleNode(int data) {
this.data = data;
}
//打印结点的数据域
public void displayNode() {
System.out.print("{" + data + "} ");
}
}
双向链表构造:
public class DoublyLinkList {
private DoubleNode first;
private DoubleNode last;
public DoublyLinkList() {
first = null;
last = first;
}
//从头部开始打印
public void displayForward() {
System.out.print("List(first--->last): ");
DoubleNode current = first;
while (current != null) {
current.displayNode();
current = current.next;
}
System.out.println();
}
//从尾部开始演绎
public void displayBackward() {
System.out.print("List(last--->first): ");
DoubleNode current = last;
while (current != null) {
current.displayNode();
current = current.prev;
}
System.out.println();
}
}
插入元素
与单向链表一样,有三种插入方法:头插法、尾插法和中间插入
头插法
// 头插法
public void insertFirst(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (first == null) {
last = newDoubleNode;
}else {
first.prev = newDoubleNode;
}
newDoubleNode.next = first;
first = newDoubleNode;
}
尾插法
// 尾插法
public void insertLast(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (first == null) {
first = newDoubleNode;
}else {
newDoubleNode.prev = last;
last.next = newDoubleNode;
}
last = newDoubleNode;
}
中间插入
// 中间插入
public void insertAfter(int key, int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
DoubleNode current = first;
while ((current != null) && (current.data != key)) {
current = current.next;
}
if (current == null) {
if (first == null) {
first = newDoubleNode;
last = newDoubleNode;
} else {
last.next = newDoubleNode;
newDoubleNode.prev = first;
last = newDoubleNode;
}
} else {
if (current == last) {
newDoubleNode.next = null;
last = newDoubleNode;
}else {
newDoubleNode.next = current.next;
current.next.prev = newDoubleNode;
}
current.next = newDoubleNode;
newDoubleNode.next = current;
}
}
删除元素
删除首元素
// 删除首元素
public DoubleNode deleteFirst() {
DoubleNode temp = first;
if (first.next == null) {
last = null;
} else {
first.next.prev = null;
}
first = first.next;
return temp;
}
删除尾元素
// 删除尾元素
public DoubleNode deleteLast() {
DoubleNode temp = last;
if (first.next == null) {
first = null;
}else {
last.prev.next = null;
}
last = last.prev;
return temp;
}
删除中间节点
public DoubleNode deleteKey(int key) {
DoubleNode current = first;
//遍历链表寻找该值所在的结点
while (current != null && current.data != key) {
current = current.next;
}
//若当前结点指向null则返回null,
if (current == null) {
return null;
} else {
//如果current是第一个结点
if (current == first) {
//则将first指向它,将该结点的previous指向null,其余不变
first = current.next;
current.next.prev = null;
} else if (current == last) {
//如果current是最后一个结点
last = current.prev;
current.prev.next = null;
} else {
//当前结点的上一个结点的next域应指向当前的下一个结点
current.prev.next = current.next;
//当前结点的下一个结点的previous域应指向当前结点的上一个结点
current.next.prev = current.prev;
}
}
return current; //返回
}