链表的核心思想
链表的核心思想
1. 数组转化为单链表
class ListNode {int val;ListNode next;ListNode(int x) {val = x};
}// 将数组转化为一个单链表
ListNode createLinkedList(int[] arr) {if (arr == null || arr.length == 0) {return null;}ListNode head = new ListNode(arr[0]);ListNode cur = head;for(int i = 1; i < arr.length; i++) {cur.next = new ListNode(arr[i]);cur = cur.next;}return head;
}
代码疑难点
cur.next = new ListNode(arr[i]); // 把新建的节点接到当前节点后面cur = cur.next; // 当前指针移动到下一个节点去
2. 单链表的头插
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在单链表前面进行头插
ListNode cur = new ListNode(int[0]); // 创建一个新节点
cur.next = head;
head = cur;
代码疑难点
1 -> 2 -> 3 -> 4 -> 5
cur.next = head; // 将新节点指向了1这个节点,这个节点也就是 head
// 变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5 // 此时,head 指针还停留在 1 这个节点上
head = cur; // 将 head 指针走向了 0 这个位置
3. 单链表的尾插
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在链表的尾部插入一个数字6
ListNode p = head; // 一般另起一个指针来进行遍历,不使用原来的头结点
// 首先,得将指针移动到链表尾部
while (p.next != null) {p = p.next;
}
// 1 -> 2 -> 3 -> 4 -> 5 // 现在 p 指针指向了 5 数字
p.next = new ListNode(6);// 这样就完成了:1 -> 2 -> 3 -> 4 -> 5 -> 6
代码疑难点
// 为什么 while(p != null || p.next != null) 不是这个?而是只需要p.next != null 即可?
// 原因就是:如果 head != null ,p 一定不会等于 null,最终会停在尾节点。
4. 在单链表中间插入新元素
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在坐标3中插入一个数字66
ListNode p = head;
for (int i = 0; i < 2; i++) {p = p.next;
}
// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next;
// 插入新节点
p.next = newNode;// 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
代码疑点
// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next; // p.next 指向了4这个数字,66 -> 4
// 插入新节点
p.next = newNode; // p 的下一个节点指向了 newNode 变成了:3 -> 66 // 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
5. 在单链表中删除元素
// 创建一个单链表
ListNode head = new ListNode(new int[]{1, 2, 3, 4, 5};
// 删除第 4 个节点,要操作前驱节点
ListNode p = head;
// 走到要删除节点的前驱节点,也就是3这个节点
for (int i = 0; i < 2; i++) {p = p.next; // 往下走
}
// 删除也就是不连接 4 这个节点得了
p.next = p.next.next; // 3 的下一个下一个节点 -> 是 5// 现在链表:1 -> 2 -> 3 -> 5
注意点:【删除】就是:将要删除的节点的前驱节点 -> 连接其后驱节点
6. 在单链表尾部删除元素
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在尾部删除元素:找到倒数第二个元素
ListNode p = head;
while(p.next.next != null) {p = p.next; // 往下走
}
// 走到这个状态:p 指向了 4
// 不指向 5 了,以 4 为结尾好了
p.next = null;// 现在链表变成了 1 -> 2 -> 3 -> 4
7. 在单链表头部删除元素
// 这个更简单,头节点往下走一步好了,这样就把头节点删掉了
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 头节点往下走一步
head = head.next;// 现在变成:2 -> 3 -> 4 -> 5
不过,读者会不会有一个疑问:前面的 1 应该还会指向 2 ,会不会造成内存泄漏?
答案是不会。旧的头节点指向别的节点没有关系,只要没有其他节点指向这个旧的头结点即可,这样,旧的头节点就会被垃圾回收收掉。
8. 双链表的基本操作
// 创建一条双链表
// 创建节点
class DoublyListNode {int val;DoublyListNode prev, next;DoublyListNode(int x) {val = x};
}
DoublyListNode createDoublyLinkedList(int arr) {// 判断if (arr == null || arr.length == 0) {return null;}// new 一个头节点DoublyListNode head = new DoublyListNode(arr[0]);DoublyListNode cur = head;// 开始创建链表for (int i = 1; i < arr.length; i++) {// 双向链表,最重要的是双向DoublyListNode newNode = new DoublyListNode(arr[i]);cur.next = newNode; // cur -> newNodenewNode.prev = cur; // cur <- newNodecur = cur.next; // cur 指针往下走}return head;
}
9. 双链表的遍历/查找/修改
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
DoublyListNode tail = null;// 从头节点向后遍历链表
for (DoublyListNode cur = head; cur != null; cur = cur.next) {System.out.println(cur.val);tail = cur; // 尾节点跟随 cur 指针的步伐
}
// 从尾节点向前遍历链表
for (DoublyListNode cur = tail; cur != null; cur = cur.prev) {System.out.println(cur.val);
}
10. 在双链表头部插入元素
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
// 先 new 一个节点 0
DoublyListNode newNode = new DoublyListNode(0);
newNode.next = head; // 0 -> 1
head.prev = newNode; // 0 <- 1
head = newNode; // head 往 newNode 走,因为 head 得变成头节点,原本 head 在 1 的位置,不符合规则,所以跑回 0 这个位置// 结果变成:0 -> 1 -> 2 -> 3 -> 4 -> 5
11. 在双链表尾部插入一个新元素
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode tail = head;// 先走到链表的尾部
while (tail.next != null) {tail = tail.next;
}
// 现在 tail 指针指向了 5 这个位置
// new 一个新的节点 6
DoublyListNode newNode = new DoublyListNode(6);
tail.next = newNode; // tail -> newNode
newNode.prev = tail; // tail <- newNode
// 现在 tail 还是指向 5
tail = newNode; // tail 顾名思义,是尾节点的意思,所以得指向 6,所以,就往 newNode 这挪,最终 tail 就指向 6// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6
12. 在双链表中间插入新元素
在双链表指定位置插入节点,需要调整该位置的前驱节点和后续节点的指针。
比如下面的例子,把元素 66 插入到索引 3(第 4 个节点)的位置:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode cur = head;
// 想要在索引 3(第 4 个节点)插入元素
// cur 得走到 索引 3 的前一个位置:索引 2
for (int i = 0; i < 2; i++) {cur = cur.next;
}
// new 一个新节点 66
DoublyListNode newNode = new DoublyListNode(66);
// 得呈现一个这个状态:cur -> newNode -> cur.next
newNode.next = cur.next; // newNode -> cur.next
cur.next = newNode; // cur -> newNode// 双链表,讲究一个双向
cur.next.prev = newNode; // newNode <- cur.next
newNode,prev = cur; // cur <- newNode// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
13. 在双链表中删除一个节点
在双链表中删除元素,得调整要删除元素的前驱节点以及其后续节点的指针:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode cur = head;
for (int i = 0; i < 2; i++) {cur = cur.next;
}
// 现在 cur 指针指向了 3
DoublyListNode toDelete = cur.next; // toDelete 节点指向 4
// 只需要将 toDelete 的前驱节点 -> 后续节点,跳过 toDelete 就好了
cur.next = toDelete.next; // cur -> toDelete.next
toDelete.next.prev = cur; // cur <- toDelete.next
// 把 toDelete 的前后指针都去置为 null (可选,一个好习惯)
toDelete.next = null;
toDelete.prev = null;// 现在链表变成了 1 -> 2 -> 3 -> 5
14. 在双链表头部删除元素
删除双链表需要调整头节点的指向
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});// 删除头节点,让 head 往下走一个步即可
DoublyListNode toDelete = head;
head = head.next;
// 现在 head 已经指向了 2
head.prev = null; // 将 2 的前驱节点置为空//清除已删除节点的指针
toDelete.next = null;// 现在链表变成了 2 -> 3 -> 4 -> 5
15. 在双链表尾部删除元素
在单链表中,由于缺乏前驱节点,所以只能遍历到尾节点的前一个节点,再进行操作,也就是:cur.next.next != null
操作它的 next 指针才能将尾节点删掉。但在双链表中,由于每个节点都存储其自身的前驱节点的指针,所以,在双链表中,可以直接操作尾节点,也就是:cur.next != null
,把自己从链表中摘掉。
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除尾节点 5
DoublyListNode cur = head;
while (cur.next != null) {cur = cur.next;
}
// 现在 cur 走到了 5 这个位置
cur.prev.next = null; // cur.prev 指向的是 4 4 的 next 就是 5 所以把 5 给断掉了cur.prev = null; // 把被删结点的指针都断开是个好习惯(可选)// 现在链表变成了 1 -> 2 -> 3 -> 4