Leetcode刷题-链表(4.22)
Leetcode023 合并k个升序链表
方法:最小堆
合并后的第一个节点一定是某个链表的头节点 h e a d i head_i headi;
合并后的第二个节点,可能是某个链表的头节点,也可能是 h e a d i head_i headi下一个节点。
例如有三个链表 1->2->5, 3->4->6, 4->5->6
,找到第一个节点 1 之后,第二个节点不是另一个链表的头节点,而是节点 1 的下一个节点 2。
那么:当找到一个节点值最小的节点x
,就把节点x.next
加入到可能最小节点的集合中。
定义一个数据结构,支持:
- 从数据结构中找到并移除最小节点
- 插入节点
使用最小堆实现。初始化先把所有链表的头节点入堆,然后不断弹出堆中最小节点x
,如果x.next
不为空就加入堆中,循环直到堆为空。弹出节点按顺序拼接即为答案。
[Java]:使用PriorityQueue实现最大最小堆
PriorityQueue(优先队列),一个基于优先级堆的无界优先级队列
实际上是一个堆(不指定Comparator时默认为最小堆),通过传入自定义的Comparator函数可以实现大顶堆。
PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(); //小顶堆,默认容量为11 PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(11,new Comparator<Integer>(){ //大顶堆,容量11 @Override public int compare(Integer i1,Integer i2){ return i2-i1; } });
PriorityQueue的常用方法有:poll(),offer(Object),size(),peek()等。
- 插入方法(offer()、poll()、remove() 、add() 方法)时间复杂度为O(log(n)) ;
- remove(Object) 和 contains(Object) 时间复杂度为O(n);
- 检索方法(peek、element 和 size)时间复杂度为常量。
public static ListNode mergeKLists(ListNode[] lists){
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(new Comparator<ListNode>(){
@Override
public int compare(ListNode o1, ListNode o2){
return o1.val - o2.val;
}
});
ListNode resList = new ListNode();
ListNode head = resList;
int nums = lists.length;
for(ListNode list:lists){
if(list!=null)
minHeap.add(list);
}
while (!minHeap.isEmpty()){
ListNode tmp = new ListNode();
tmp = minHeap.poll();
if(tmp.next==null){
resList.next = tmp;
resList = resList.next;
continue;
}
minHeap.add(tmp.next);
tmp.next = null;
resList.next = tmp;
resList = resList.next;
}
return head.next;
}
双向链表
Leetcode146 LRU缓存
LRU缓存机制,即采用最近最少使用的缓存策略。它的原则是如果一个数据最近没有被访问到,那么将来被访问到的几率也很小。当限定内存空间中没有其它空间可用时,应该把很久没有访问到的数据去除掉。
题目要求:
get
和put
都是O(1)
时间复杂度
使用双向链表实现,每个节点表示一对key-value
。每个节点都有两个指针prev
和next
分别指向前一个和下一个节点。此外,链表中包含一个哨兵节点。这样可以使每个节点的prev
和next
都不为空,简化代码逻辑。使用HashMap直接映射,用空间换时间。
-
如何快速找到要查找的值?
把key映射到链表中的对应节点
这可以使用哈希表实现
-
需要几个哨兵节点?
一个。一开始哨兵节点
dummy
的prev
和next
都指向dummy
。对着节点的插入,dummy
的next
指向链表中第一个节点,prev
指向链表最后一个节点。 -
为什么节点要把
key
保存下来?在删除链表末尾节点时,也要删除哈希表中的记录,这需要知道末尾节点的
key
import java.util.HashMap;
import java.util.Map;
public class A0422LRUCache {
// 定义链表节点
private static class Node{
int key;
int 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> hashmap = new HashMap<>();
// 用正整数作为容量capacity初始化LRU缓存
public A0422LRUCache(int capacity){
this.capacity = capacity;
dummy.next = dummy;
dummy.prev = dummy;
}
// 如果关键字key存在于缓存中,则返回关键字值,否则返回-1
public int get(int key){
if(hashmap.containsKey(key)){
Node tmp = hashmap.get(key);
int res = tmp.value;
// TODO:处理链表中节点顺序
removeNode(tmp);
addHeadNode(tmp);
return res;
}
// key不存在返回-1
return -1;
}
// 如果关键字key已经存在,则变更其数据值;如果不存在,则向缓存中插入key-value
// 如果插入操作导致关键字数量超过capacity,则应该逐出最久未使用的关键字
public void put(int key, int value){
if(hashmap.containsKey(key)){
//hashmap.get(key).value = value;
// TODO:处理链表的节点顺序
Node tmp = hashmap.get(key);
tmp.value = value;
removeNode(tmp);
addHeadNode(tmp);
}
else {
// TODO:创建新的节点并加入hashmap,加入链表的头节点处
Node tmp = new Node(key, value);
hashmap.put(key, tmp);
addHeadNode(tmp);
// 容量超出处理
if(hashmap.size() > capacity){
// 删除链表最后一个节点
Node lastNode = dummy.prev;
removeNode(lastNode);
hashmap.remove(lastNode.key);
}
}
}
public void removeNode(Node node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 添加节点到链表头
public void addHeadNode(Node node){
node.next = dummy.next;
node.prev = dummy;
dummy.next = node;
node.next.prev = node;
}
}
Leetcode460 LFU缓存
LFU缓存机制,即最不经常使用的数据淘汰。
**LFU和LRU的区别在于:**LRU淘汰的是最久未访问的数据,LFU淘汰的是最久未访问到的数据(当两个或多个数据的使用频率相同时,LFU会再选择最久未访问的数据进行淘汰)
在节点新增加一个代表访问频率的变量,每次节点被访问时,令该节点的访问频率加1.
题目要求:
get
和put
都是O(1)
时间复杂度。
可以使用双向链表实现,每个节点都表示key-value-count
。每个节点都有两个指针prev
和next
分别指向前一个和下一个节点。此外,链表中还包含一个哨兵节点,可以让每个节点的prev
和next
都不为空,从而简化代码逻辑。
import java.util.HashMap;
import java.util.Map;
public class A0422LFUCache {
public static class Node{
// 新添加进来的数据访问次数为1
int key, value, freq = 1;
Node prev, next;
Node(int key, int value){
this.key = key;
this.value = value;
}
}
// LFU的容量
private final int capacity;
// keyToNode:实现O(1)时间复杂度的存取
private final Map<Integer, Node> keyToNode = new HashMap<>();
// freqToDumpy:实现对不同访问频次的链表头节点定位
private final Map<Integer, Node> freqToDumpy = new HashMap<>();
// 当前所有节点的最小访问次数
private int minFreq = 1;
public A0422LFUCache(int capacity){
this.capacity = capacity;
Node dummy = new Node(0,0);
dummy.next = dummy;
dummy.prev = dummy;
freqToDumpy.put(minFreq, dummy);
}
public int get(int key){
if(!keyToNode.containsKey(key)){
return -1;
}
Node tmp = keyToNode.get(key);
// tmp.freq += 1;
transferToList(tmp);
return tmp.value;
}
public void put(int key, int value){
if(keyToNode.containsKey(key)){
Node tmp = keyToNode.get(key);
tmp.value = value;
transferToList(tmp);
}
else {
// 先检查LFU容量
if(keyToNode.size() == capacity){
// 删除keyToNode中多余的key-Node
int k = freqToDumpy.get(minFreq).prev.key;
keyToNode.remove(k);
// 删除多余节点
removeLastFreqNode();
}
// 新节点,直接添加到freq为1的链表中
// 将minFreq的值更新为1
Node tmp = new Node(key, value);
keyToNode.put(key, tmp);
// freqToDumpy.get(1) --> get(minFreq)
addHeadNode(tmp, freqToDumpy.get(1));
minFreq = 1;
}
}
public void removeNode(Node node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
public void addHeadNode(Node node, Node dummy){
node.next = dummy.next;
node.prev = dummy;
dummy.next = node;
node.next.prev = node;
}
// 节点在链表中的转移
public void transferToList(Node tmp){
if(freqToDumpy.containsKey(tmp.freq+1)){
// 直接操作
Node srcDummy = freqToDumpy.get(tmp.freq);
Node tgtDummy = freqToDumpy.get(tmp.freq + 1);
// TODO:从srcDummy中删除,在tgtDummy头添加
removeNode(tmp);
if(srcDummy == srcDummy.next && tmp.freq == minFreq){
minFreq++;
}
tmp.freq++;
addHeadNode(tmp, tgtDummy);
}
else {
// TODO:创建新链表,保存访问次数为tmp.freq的节点,并将新链表头节点保存到map中
Node tgtDummy = new Node(0, 0);
tgtDummy.next = tgtDummy;
tgtDummy.prev = tgtDummy;
freqToDumpy.put(tmp.freq + 1, tgtDummy);
removeNode(tmp);
Node srcDummy = freqToDumpy.get(tmp.freq);
if(srcDummy == srcDummy.next && tmp.freq == minFreq){
minFreq++;
}
tmp.freq++;
addHeadNode(tmp, tgtDummy);
}
}
public void removeLastFreqNode(){
// 更新当前minFreq对应链表的最后一个节点
Node dummy = freqToDumpy.get(minFreq);
removeNode(dummy.prev);
if(dummy == dummy.next){
minFreq++;
}
}
}
参考链接:https://github.com/EndlessCheng/codeforces-go/blob/master/leetcode/SOLUTIONS.md