链表常见算法
链表结构
Java
publicclassListNode {
intval;
ListNode next;
ListNode(intx) {
val = x;
}
publicvoidprintList() {
ListNode node = this;
while(node != null) {
System.out.print(node.val + " ");
node = node.next;
}
System.out.println();
}
}
使用链表实现LRU
缓存清理策略: 先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。
维护一个有序单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始顺序遍历链表。
- 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
- 如果此数据没有在缓存链表中,又可以分为两种情况:
- 如果此时缓存未满,则将此结点直接插入到链表的头部;
- 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
Java
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
//构造方法中指定 accessOrder 为 true ,这样在访问元素时就会把该元素移动到链表尾部,链表首元素就是最近最少被访问的元素;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
/**
* 判断size超过容量时返回true,告知LinkedHashMap移除最老的缓存项(即链表的第一个元素)
*/
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
单链表反转
- 核心是维护一个前置指针prev
Java
publicclassLinkedListReversal {
publicstaticvoidmain(String[] args) {
// 创建一个简单的链表 1 -> 2 -> 3 -> 4 -> 5
ListNode head = newListNode(1);
head.next = newListNode(2);
head.next.next = newListNode(3);
head.next.next.next = newListNode(4);
head.next.next.next.next = newListNode(5);
System.out.println("原始链表:");
head.printList();
// 反转链表
ListNode reversedHead = reverseList(head);
System.out.println("反转后的链表:");
reversedHead.printList();
}
/**
* 核心是维护一个前向指针prev
* 时间复杂度O(n), 空间复杂度O(1)
* @param head
* @return
*/
privatestaticListNode reverseList(ListNode head) {
ListNode prev = null;
while(head != null) {
ListNode temp = head.next;
head.next = prev;
prev = head;
head = temp;
}
returnprev;
}
}
链表中环的检测(Floyd判圈算法)
- 核心是快慢指针,快的每次移动两节点,慢的每次移动一节点, 若链表成环,则快指针在到达尾节点之前一定会追上慢指针;简单理解,两个人绕操场跑圈,只要一直跑,快的人一定会追上慢的,而且会套一圈
- 为什么快指针会在慢指针进入环内的第一圈就相遇?
假设环周长为n,链表起点距离环入口为m,第一次相遇时慢指针在环内走过的距离为k,由于快指针走过的距离是慢指针2倍,由于第一次相遇,快指针一定套慢指针一圈,得到**
$$ 2(m+k)=m+k+n $$
,可得 $$ k=n-m $$
**,由于m>0,因此k<n,因此第一次相遇时慢指针一定未走完一圈
- 定位环入口?
由上公式同得** $$ m=n-k $$
**,当两个节点第一次相遇时,由任意一个节点回到起点,然后两个指针以同样的速度移动m个节点,即可相遇,该相遇的节点就是环入口
Java
publicclassLinkedListCycleDetection {
/**
* 检测链表中是否有环, 核心是快慢指针
* 时间复杂度O(n), 空间复杂度O(1)
*
* @param head
* @return
*/
publicstaticbooleanhasCycle(ListNode head) {
if(head == null|| head.next == null) {
returnfalse; // 空链表或只有一个节点的链表不可能有环
}
ListNode fast = head;
ListNode slow = head;
while(fast != null&& fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast.val == slow.val) {
returntrue;
}
}
returnfalse;
}
// 测试代码
publicstaticvoidmain(String[] args) {
// 创建一个有环的链表 1 -> 2 -> 3 -> 4 -> 5 -> 3(形成环)
ListNode head = newListNode(1);
head.next = newListNode(2);
head.next.next = newListNode(3);
head.next.next.next = newListNode(4);
head.next.next.next.next = newListNode(5);
head.next.next.next.next.next = head.next.next; // 形成环
System.out.println("链表中是否有环: " + hasCycle(head));
}
}
两个有序的链表合并
Java
publicclassMergeTwoSortedLists {
/**
* 合并两个有序链表
* 时间复杂度O(n+m), 最快的情况下两个链表每个节点都被访问一次
* 空间复杂度O(1)
* @param l1
* @param l2
* @return
*/
publicstaticListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 创建一个哨兵节点作为结果链表的头结点
ListNode prev = newListNode(0);
ListNode current = prev;
// // 当两个链表都不为空时,比较当前节点值,并将较小值加入到结果链表中
while(l1 != null&& l2 != null) {
if(l1.val < l2.val) {
current.next = l1;
l1 = l1.next;
} else{
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
// 将剩余的节点连接到结果链表后面
if(l1 != null) {
current.next = l1;
} else{
current.next = l2;
}
// 返回合并后的链表(不包含哨兵节点)
returnprev.next;
}
// 测试代码
publicstaticvoidmain(String[] args) {
// 创建两个有序链表
ListNode l1 = newListNode(1);
l1.next = newListNode(3);
l1.next.next = newListNode(5);
ListNode l2 = newListNode(2);
l2.next = newListNode(4);
l2.next.next = newListNode(6);
System.out.println("原始链表1:");
l1.printList();
System.out.println("原始链表2:");
l2.printList();
// 合并两个链表
ListNode mergedList = mergeTwoLists(l1, l2);
System.out.println("合并后的链表:");
mergedList.printList();
}
}
删除链表倒数第n个结点
- 快慢指针,让第一个节点走n步,然后两个节点一起走,当第一个节点走到末尾时,第二个节点就刚好处于倒数第n个节点
Java
/**
* 快慢指针
* @param head
* @param n
* @return
*/
publicstaticListNode removeNthFromEnd(ListNode head, intn) {
ListNode dummy = newListNode(0);
dummy.next = head;
ListNode first = dummy;
ListNode second = dummy;
for(inti = 0; i <= n; i++) {
first = first.next;
}
while(first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
returndummy.next;
}
求链表的中间结点
- 快慢指针,快指针每次走两步,慢指针每次走一步,快指针到尾节点时,慢指针刚好在中间节点
Java
publicstaticListNode middleNode(ListNode head) {
ListNode slow = head, fast = head;
while(fast != null&& fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
returnslow;
}