本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答,01~07为C++语言,08及以后为Java语言。
01 合并 K 个升序链表


java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
}
}
方法一:遍历 + 合并两个有序链表
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int k = lists.length;
ListNode dummy = null;
for(int i=0; i<k; i++){
dummy = mergeTwoLists(dummy, lists[i]);
}
return dummy;
}
//合并两个有序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2){
ListNode l3 = new ListNode(0);
ListNode flag = l3; //return flag.next
while(l1 != null && l2 != null){
if(l1.val < l2.val){
l3.next = l1;
l1 = l1.next;
}else{
l3.next = l2;
l2 = l2.next;
}
l3 = l3.next;
}
if(l1 != null){
l3.next = l1;
}
if(l2 != null){
l3.next = l2;
}
return flag.next;
}
}
方法二:二分法(递归)+ 合并两个有序链表
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length-1);
}
public ListNode merge(ListNode[] lists, int left, int right){
if(left == right){
return lists[left];
}
if(left > right){
return null; //为啥捏?
}
int mid = (left + right) / 2;
return mergeTwoLists(merge(lists, left, mid), merge(lists, mid+1, right));
}
//合并两个有序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2){
ListNode l3 = new ListNode(0);
ListNode flag = l3; //return flag.next
while(l1 != null && l2 != null){
if(l1.val < l2.val){
l3.next = l1;
l1 = l1.next;
}else{
l3.next = l2;
l2 = l2.next;
}
l3 = l3.next;
}
if(l1 != null){
l3.next = l1;
}
if(l2 != null){
l3.next = l2;
}
return flag.next;
}
}
在什么情况下left > right
,为什么要返回null
值?
当你调用 mergeKLists
时,假设 lists
为空数组,即 lists.length == 0
,那么初始调用 merge(lists, 0, -1)
就会发生 left > right
的情况,在这种情况下,返回 null
是合理的,因为没有链表可以合并。
02 LRU 缓存


java
class LRUCache {
public LRUCache(int capacity) {
}
public int get(int key) {
}
public void put(int key, int value) {
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
方法:哈希映射 + 双向链表
java
class LRUCache {
//1.自定义双向链表结点
class DLinkedNode{
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value){
this.key = _key;
this.value = _value;
}
}
//2.自定义哈希映射 cache <int, DLinkedNode>
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size, capacity;
private DLinkedNode head, tail;
/*
3.初始化LRU缓存
a.初始化 size, capacity
b.初始化 head, tail
*/
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
/*
4.查询操作:如果key在cache中,返回value;否则,返回-1
a. node = null, return -1
b. node != null, move to head, return node.value
*/
public int get(int key) {
DLinkedNode node = cache.get(key);
if(node == null){
return -1;
}
/*
move to head
a. remove node
b. add to head
*/
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
return node.value;
}
/*
5.更新操作:如果key在cache中,更新value;否则,插入cache <key, nodeNew>
a. node = null
i.创建 nodeNew
ii.插入 cache <key, nodeNew>, add to head
iii. size > capacity, remove tail
b. node != null, move to head
*/
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if(node == null){
DLinkedNode nodeNew = new DLinkedNode(key, value);
cache.put(key, nodeNew);
++size;
// add to head
nodeNew.prev = head;
nodeNew.next = head.next;
head.next.prev = nodeNew;
head.next = nodeNew;
if(size > capacity){
DLinkedNode res = tail.prev;
//remove node
res.prev.next = res.next;
res.next.prev = res.prev;
cache.remove(res.key);
--size;
}
}
else{
node.value = value;
/*
move to head
a. remove node
b. add to head
*/
node.prev.next = node.next;
node.next.prev = node.prev;
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
① 为什么要采用双向链表,而不是单向链表?
- 快速删除:双向链表可以从链表中的任何位置快速移除节点,因为每个节点都有一个指向前驱节点的指针,而单向链表则需要从头开始遍历才能找到前驱节点。
- 移动节点到头部:在LRU缓存中,频繁的操作是将节点移动到链表头部。如果采用单向链表,这个操作会更复杂且效率低下,而双向链表可以在常数时间内完成。
② 为什么要将DLinkedNode定义为内部类?
- 封装 :
DLinkedNode
仅在LRUCache
中使用,将其作为内部类可以更好地实现封装。 - 简化代码:在内部类中可以直接访问外部类的私有成员和方法,简化了代码的结构,而无需额外的getter/setter。
- 逻辑关系清晰 :
DLinkedNode
本质上是LRUCache
的一部分,通过内部类的方式可以更明确地表达这种包含关系。