19 相交链表 ⭐
链表中的经典题
当我在我的路上走过一遍依然没有遇见你时,那么我会接着来到你走过的路走一遍,而你如果也和我一样心有灵犀,那么总有一天我们将在合适的时候相遇。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
ListNode q = headB;
while (p != q){
p = p == null ? headB : p.next;
q = q == null ? headA : q.next;
}
return p;
}
空间复杂度 O(1)
20 反转链表 ⭐
迭代版本如下,循环结束时,cur = null,pre 是头节点。
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null){
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
递归版本:
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null){
return head;
}
// 向下递归,拿到链表的头节点
ListNode p = reverseList(head.next);
// 反转当前节点
head.next.next = head;
head.next = null;
// 返回头节点
return p;
}
21 回文链表
题目进阶要求 O(1) 的空间复杂度,因此可以反转一半的链表,然后同时遍历判断是否回文。
如何查找链表的中心节点呢?可以通过快慢指针:876. 链表的中间结点
public boolean isPalindrome(ListNode head) {
ListNode mid = middleNode(head);
ListNode h2 = reverse(mid);
while (h2 != null){
if (h2.val != head.val){
return false;
}
h2 = h2.next;
head = head.next;
}
return true;
}
// 链表的中间节点
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// 反转链表
public ListNode reverse(ListNode head){
ListNode pre = null;
ListNode cur = head;
while (cur != null){
ListNode nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
22 环形链表
题目进阶要求 O(1) 的空间复杂度,可以使用快慢指针解决。
如果有环的话,那么兔子和乌龟都会进入环中。这时用「相对速度」思考,乌龟不动,兔子相对乌龟每次只走一步,这样就可以看出兔子一定会和乌龟相遇了。
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;
}
26 删除链表的倒数第 N 个结点
双指针做法:
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode left = dummy;
ListNode right = dummy;
// 右指针先走 n 步
for (int i = 0; i < n; i++){
right = right.next;
}
// 左右指针一起走
while (right.next != null){
left = left.next;
right = right.next;
}
// 删除倒数第 n 个节点
left.next = left.next.next;
return dummy.next;
}
27 两两交换链表中的节点
32 LUR 缓存 ⭐
本题使用双向链表实现:
get:把节点抽出来,放到链表头部
put:如果已经存在就抽出来放到头部并更新val,否则直接插入链表头部
使用哈希表快速根据 key 获得节点。
class LRUCache {
private static class Node {
int key, value;
Node prev, next;
Node(int k, int v) {
key = k;
value = v;
}
}
private final int capacity;
private final Node dummy = new Node(0, 0); // 哨兵节点
private final Map<Integer, Node> keyToNode = new HashMap<>();
public LRUCache(int capacity) {
this.capacity = capacity;
dummy.next = dummy;
dummy.prev = dummy;
}
public int get(int key) {
Node node = getNode(key);
return node == null ? -1 : node.value;
}
// 获取 key 对应的节点,同时把该节点移到链表头部
private Node getNode(int key) {
if (!keyToNode.containsKey(key)) {
return null;
}
Node node = keyToNode.get(key);
remove(node);
addFirst(node);
return node;
}
public void put(int key, int value) {
Node node = getNode(key);
// 已经存在,直接修改值
if (node != null) {
node.value = value;
return;
}
// 插入链表头部,并更新哈希表
node = new Node(key, value);
addFirst(node);
keyToNode.put(key, node);
// 达到容量上限,则删除最后一个节点
if (keyToNode.size() > capacity) {
Node last = dummy.prev;
remove(last);
keyToNode.remove(last.key);
}
}
// 删除一个节点
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 在链表头添加一个节点
private void addFirst(Node node) {
node.prev = dummy;
node.next = dummy.next;
node.prev.next = node;
node.next.prev = node;
}
}