1 链表中环的问题
1.1 判断链表是否有环
leetCode141,给定一个链表,判断链表中是否有环。
1.1.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;
}
1.1.2 Hash解法
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;
}
1.2 确定入口的方法
在确定了链表是个环,那么我们下一步就是应该去找到这个环的位置。
结论:先按照快慢方式寻找到相遇的位置(假如为下图中Z),然后将两指针分别放在链表头(X)和相遇位置(Z),并改为相同速度推进,则两指针在环开始位置相遇(Y)。
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 ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
2 双链表
双链表问题考的不是很多。
原理跟单链表其实一致,但是多了一个“回退操作”,pre指向前一个结点。
2.1 定义双指针
class DoubleNode {
public int data; //数据域
public DoubleNode next; //指向下一个结点
public DoubleNode prev;//指向前一个结点
public DoubleNode(int data) {
this.data = data;
}
}
2.2 双指针的插入
头尾插入
//头部插入
public void insertFirst(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (first == null) {
last = newDoubleNode;
} else {//如果不是第一个结点的情况
//将还没插入新结点之前链表的第一个结点的previous指向newNode
first.prev = newDoubleNode;
}
newDoubleNode.next = first;
//将新结点赋给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指向newNode
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;
}
//若当前结点current为空
if (current == null) {
if (first == null) {
first = newDoubleNode;
last = newDoubleNode;
} else {
//2、找不到key值,则在链表尾部插入一个新的结点
last.next = newDoubleNode;
newDoubleNode.prev = last;
last = newDoubleNode;
}
} else {//第3种情况,找到了key值,分两种情况
if (current == last) {
//1、key值与最后结点的data相等
newDoubleNode.next = null;
last = newDoubleNode;
} else {
//2、两结点中间插入
newDoubleNode.next = current.next;
current.next.prev = newDoubleNode;
}
current.next = newDoubleNode;
newDoubleNode.prev = current;
}
}
2.3 双指针的删除
双向链表的不足就是增删改的时候,需要修改的指针多了,操作更麻烦了。由于双向链表在算法中不是很重要,我们先看一下删除的大致过程。首尾元素的删除还比较简单,直接上代码:
//删除首元素
public DoubleNode deleteFirst() {
DoubleNode temp = first;
//若链表只有一个结点,删除后链表为空,将last指向null
if (first.next == null) {
last = null;
} else {
//若链表有两个及以上的结点 ,因为是头部删除,则first.next将变成第一个结点,其previous将变成null
first.next.prev = null;
}
//将first.next赋给first
first = first.next;
//返回删除的结点
return temp;
}
//从尾部删除结点
public DoubleNode deleteLast() {
DoubleNode temp = last;
//如果链表只有一个结点,则删除以后为空表,last指向null
if (first.next == null) {
first = null;
} else {
//将上一个结点的next域指向null
last.prev.next = null;
}
//上一个结点称为最后一个结点,last指向它
last = last.prev;
//返回删除的结点
return temp;
}
我们再看删除中间元素的情况,要标记出几个关键结点的位置,也就是图中的cur,cur.next和cur.prev结点。由于在双向链表中可以走回头路,所以我们使用cur,cur.next和cur.prev任意一个位置都能实现删除。假如我们就删除cur,图示是这样的:
我们只需要调整两个指针,一个是cur.next的prev指向cur.prev,第二个是cur.prev的next指向cur.next。此时cur结点没有结点访问了,根据垃圾回收算法,此时cur就变得不可达,最终被回收掉,所以这样就完成了删除cur的操作。**想一下,这里调整两条线的代码是否可以换顺序?
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; //返回
}