链表中环的问题与双向链表

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,图示是这样的:

image.png

我们只需要调整两个指针,一个是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; //返回

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值