理解好这个与单向链表的差异很关键🙂

一、手动实现
java
package structure;
public class MyLinkedList2 {
// 标记头
public ListNode head;
// 标记尾
public ListNode last;
// 结点类定义
static class ListNode {
public int val;
public ListNode next;
public ListNode prev;
public ListNode(int val) {
this.val = val;
}
}
// 头插法
public void addFirst(int data) {
ListNode n = new ListNode(data);
//头结点为空,新节点既当头又当尾
if(head == null){
head = n;
last = n;
} else {
n.next = head;
head.prev = n;
head = n;
}
}
// 尾插法
public void addLast(int data) {
ListNode n = new ListNode(data);
//用不到head
if(last == null){
head = n;
last = n;
} else {
last.next = n;
n.prev = last;
last = n;
}
}
//得到单链表结点的数量
public int size() {
int count = 0;
ListNode cur = head;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
// 任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
int size = size();
if (index < 0 || index > size){
System.out.println("index不合法");
return;
}
if (index == 0){
addFirst(data);
return;
}
if (index == size){
addLast(data);
return;
}
//找到index下标的地址curN
ListNode curN = findIndex(index);
ListNode n = new ListNode(data);
n.next = curN;
curN.prev.next = n;
n.prev = curN.prev;
curN.prev = n;
}
//寻找index下标的地址curN
public ListNode findIndex(int index){
ListNode cur = head;
while (index != 0){
cur = cur.next;
index--;
}
return cur;
}
// 查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
ListNode cur = head;
while (cur != null){
if (cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
// 删除第一次出现关键字为key的节点
public void remove(int key) {
ListNode del = head;
while (del != null) {
if (del.val == key) {
//情况一:删除头,并保证它不能为空
if (del == head) {
head = head.next;
if (head == null) {
last = null;
} else {
head.prev = null;
}
}
//情况二:删除尾
else if (del == last) {
last = last.prev;
last.next = null;
}
//情况三:删除中间
else {
ListNode before = del.prev;
before.next = del.next;
del.next.prev = before;
}
//删除一个后就退出
return;
}
del = del.next;
}
}
public void show(){
ListNode cur = head;
while (cur != null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
public void clear(){
head = null;
last = null;
}
public static void main(String[] args) {
MyLinkedList2 m = new MyLinkedList2();
m.addLast(48);
m.addLast(48);
m.addLast(36);
m.addLast(48);
m.addLast(48);
m.show();
m.remove(48);
m.show();
}
}
二、与单向链表方法的对比图
1、头插法

假如head为空,应当设置新节点既是头又是尾
倘若没考虑到,head.prev就相当于是null.prev,代码空指针异常
2、尾插法

跟上面一个道理
3、指定位置插入中间元素

依旧遵循先绑定后面的原则,注意先后次序
4、删除遇到的第一个值为key的结点

分为三种情况,图中仅展示了删除中间结点
删除头结点的时候还要考虑到头结点为空的情况
它跟单链表删除的最大区别在于😮:
单链表需要找到一个前置结点来完成删除操作
双向链表本身就是双向的,不用单独找
5、删除所有值为key的结点

只需要把上图对应的方法中,限制只执行一次删除的return去掉,就可以执行循环删除的逻辑了
三、单链表的习题
5、合并两个有序链表

java
//合并两个有序链表
public Node mergeTwoLists(Node list1, Node list2) {
Node newH = new Node();
Node tmp = newH;
while (list1 != null && list2!= null){
if(list1.val < list2.val){
tmp.next = list1;
list1 = list1.next;
}else {
tmp.next = list2;
list2 = list2.next;
}
tmp = tmp.next;
}
if(list1 == null){
tmp.next = list2;
}
if(list2 == null){
tmp.next = list1;
}
return newH.next;
}
这里使用了傀儡结点newH,它的next来存放头结点地址,让tmp代替newH往下走,一直到某个链表为空就合并完成了
6、回文链表

java
//回文链表
public boolean isPalindrome(Node head) {
Node slow = head;
Node fast = head;
if(head == null || head.next == null){
return true;
}
//判断条件要奇偶兼容
//1、找到中间结点slow
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//2、翻转链表
Node cur = slow.next;
while (cur != null){
Node curN = cur.next;
cur.next = slow;
slow = cur;
cur = curN;
}
//3、判断回文结构
while (head != slow){
if(head.val != slow.val){
return false;
}
//需要考虑偶数情况下的判断
if(head.next == slow){
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
这个相当于是之前两个题的缝合,找中间结点,翻转,最后回文
可以参考之前单链表的这两个题目解法来理解
第1、3步骤,需要考虑奇偶的场景
7、链表相交(较简单)

java
//链表相交
public Node getIntersectionNode(Node headA, Node headB) {
Node pl = headA;
Node ps = headB;
int linA = 0;
int linB = 0;
//确保两个链表不为空
if(headA == null || headB == null){
return null;
}
while(pl != null){
pl = pl.next;
linA++;
}
pl = headA;
while(ps != null){
ps = ps.next;
linB++;
}
ps = headB;
int lin = linA-linB;
if(lin<0){
pl = headB;
ps = headA;
lin = linB-linA;
}
//走到这里保证了pl是长链表,ps是短的
//让pl先走差值步
while(lin != 0 ){
lin--;
pl = pl.next;
}
//用值比较不安全,不能证明这是同一个结点
while(pl != ps){
pl = pl.next;
ps = ps.next;
}
// pl == ps: 可能是交点,也可能都是null(不相交)
return pl;
}
主要逻辑就是让长链表先走差值步,最后两个一起走,找到一样的结点即相交结点
8、环形链表Ⅱ
首先要知道一个数学原理,在快慢指针相遇后:
从头节点和相遇点同时出发,每次走1步,它们再次相遇的位置就是环的入口

java
//环形链表Ⅱ
public Node detectCycle(Node head) {
Node fast = head;
Node slow = head;
//保证头不为空
if(head == null || head.next == null){
return null;
}
while (fast != null && fast.next != null){
//结束情况一:循环走完结束
fast = fast.next.next;
slow = slow.next;
//结束情况二:有环,直接退出
if(fast == slow){
break;
}
}
//确保是因为二结束
if (fast == slow){
fast = head;
while (fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
return null;
}
返回的是环的入口节点,没有环就是null
也有个更简洁的写法:
java
简洁版本
public Node detectCycle(Node head) {
if (head == null || head.next == null) {
return null;
}
Node fast = head;
Node slow = head;
while (fast != null && fast.next != null) {
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;
}
}
本章完
