序
作为一种基本数据结构,链表(Linked List)是一种物理存储单元上非连续的存储结构,元素的逻辑顺序是通过链表的指针来实现的。这种存储方式使链表具备插入数据复杂度 O(1) 查找复杂度 O(n) 的特性。链表一般分为三种:单向链表、双向链表、循环链表。
一、单向链表
单向链表是一种线性表,由节点(ListNode)组成,节点是由存储的对象(object)以及对下一个节点的引用(next)组成
package LinkedList;
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
区别于数组,我们无法直接访问链表里的元素,必须通过遍历来访问,复杂度是 O(n) 。
- 插入:在链表尾部插入元素 O(n),在链表头部插入元素 O(1)。
// insert 在链表尾部插入元素
public ListNode insertListNode(ListNode head, int value) {
ListNode target = new ListNode(value);
if (null == head) {
return target;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = target;
return head;
}
// insert 在链表头部插入元素
public ListNode insertHeadListNode(ListNode head, int value) {
ListNode target = new ListNode(value);
target.next=head;
return target;
}
- 删除: 删除指定下标的元素 复杂度 O(n) pre.next = cur.next
// delete
public ListNode deleteListNode(ListNode head, int index) {
if (index < 0) {
return head;
}
ListNode temp = new ListNode(-1);
temp.next = head;
ListNode pre = temp;
ListNode cur = head;
int i = 0;
while (cur != null) {
if (i == index) {
pre.next = cur.next;
return temp.next;
}
pre = cur;
cur = cur.next;
i++;
}
return temp.next;
}
- 反转链表
// Reverse
public ListNode reverseListNode(ListNode head) {
ListNode pre = null;
ListNode cur = head;
// 维护 pre cur
while (cur != null) {
// 临时变量next
ListNode next = cur.next;
// 更新cur pre
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
- 在有序链表中删除重复元素:
83. Remove Duplicates from Sorted List 题解
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = head;
ListNode cur = head.next;
while (cur != null) {
if (cur.val == pre.val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return head;
}
}
- 递归从尾到头输出链表:
public void printReverse(ListNode head) {
if (null != head) {
printReverse(head.next);
System.out.println(head.val);
}
}
- 实现栈的三种方式
- 相交链表:先计算长度,在遍历
- 快慢指针:维护 slow 和 fast 两个指针
Middle of the Linked List 求中间节点开始的链表,题解:慢指针移动一步,快指针移动两步。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slowhead = head;
ListNode fasthead = head;
while (fasthead.next != null && fasthead.next.next != null) {
slowhead = slowhead.next;
fasthead = fasthead.next.next;
}
if (fasthead.next == null)
return slowhead;
else {
return slowhead.next;
}
}
}
Linked List Cycle 判断链表是否有环,题解:慢指针移动一步,快指针移动两步
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
类似的求倒数第k个元素,维护 slow 和 fast 两个指针,fast 比 slow 快 k 步,当 fast 到达尾部时,slow即为所求值
Linked List Cycle II 判断是否有环,如果有返回起点,如果没有返回null 题解:
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
// 如果有相等的,表示此链表由环
if (slow == fast) {
break;
}
}
// 从起点head 和 第一次相遇点 同时向前走最终相遇于入口
while (head != null && slow != null) {
if (head == slow) {
return slow;
}
head = head.next;
slow = slow.next;
}
return null;
}
- 链表排序:Sort List 题解:下面只是一种最基本的排序算法,应用到链表里而已
public ListNode sortList(ListNode head) {
if (head == null) {
return null;
}
ListNode cur = head;
while (cur.next != null) {
ListNode next = cur.next;
while (next != null) {
if (cur.val > next.val) {
int tmp = cur.val;
cur.val = next.val;
next.val = tmp;
}
next = next.next;
}
cur = cur.next;
}
return head;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (k <= 1) {
return head;
}
int count = 0;
ListNode res = head;
ListNode tmp = null;
ListNode pre = tmp;
while (head != null) {
if (count == 0) {
tmp = head;
}
count++;
head = head.next;
if (count == k) {
count = 0;
ListNode temp = reverse(tmp, k);
if (pre == null) {
res = temp;
} else {
pre.next = temp;
}
pre = tmp;
// 第一个head作为头结点
}
}
if (null != pre && null != tmp&&pre!=tmp) {
pre.next = tmp;
}
return res;
}
private static ListNode reverse(ListNode head, int k) {
ListNode pre = null;
ListNode cur = head;
if (head.next == null) {
return head;
}
ListNode next = head.next;
int count = 0;
while (cur != null && count < k) {
cur.next = pre;
pre = cur;
cur = next;
if (cur == null) {
break;
}
next = next.next;
count++;
}
return pre;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
Queue<ListNode> priorityQueue=new PriorityQueue<>((a,b)->{return a.val-b.val;});
for (ListNode head:lists){
while (head != null) {
priorityQueue.offer(head);
head=head.next;
}
}
ListNode res=new ListNode(-1);
ListNode pre=res;
while (!priorityQueue.isEmpty()){
ListNode cur=priorityQueue.poll();
pre.next=cur;
cur.next=null;
pre=pre.next;
}
return res.next;
}
}
总结
- 调用节点前,一定要判断节点是否为空
- 快慢指针时,判断条件为 while (fast != null && fast.next != null),检查 fast 和 fast.next 不为空
- 快慢指针可以找环,可以找中间点,可以找相差k节点的链表
- 回文链表:需要找到中点节点,反转半段,在遍历对比
双端链表:在链表内添加了对链表尾部的引用,方便操作尾部元素,可以用来实现队列
二、双向链表
双向链表,由节点(ListNode)组成,节点是由存储的对象(object),指向下一个节点的引用(next)以及指向上一个节点的引用(pre),所以双向链表可以从两个方向进行遍历。
package LinkedList;
public class DoubleEndListNode {
int val;
DoubleEndListNode next;
DoubleEndListNode prev;
DoubleEndListNode(int x) {
val = x;
}
}
三、循环链表
循环链表指的是在单向链表和双向链表的基础上,将最后一个节点指向头结点,实现循环链表。
还有很多经典例子,不断更新中