Leetcode刷题-链表(4.22)

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缓存机制,即采用最近最少使用的缓存策略。它的原则是如果一个数据最近没有被访问到,那么将来被访问到的几率也很小。当限定内存空间中没有其它空间可用时,应该把很久没有访问到的数据去除掉。

image-20240422151931660

题目要求:getput都是O(1)时间复杂度

使用双向链表实现,每个节点表示一对key-value。每个节点都有两个指针prevnext分别指向前一个和下一个节点。此外,链表中包含一个哨兵节点。这样可以使每个节点的prevnext都不为空,简化代码逻辑。使用HashMap直接映射,用空间换时间。

image-20240422154536471

  • 如何快速找到要查找的值?

    把key映射到链表中的对应节点

    这可以使用哈希表实现

  • 需要几个哨兵节点?

    一个。一开始哨兵节点dummyprevnext都指向dummy。对着节点的插入,dummynext指向链表中第一个节点,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.

题目要求:getput都是O(1)时间复杂度。

可以使用双向链表实现,每个节点都表示key-value-count。每个节点都有两个指针prevnext分别指向前一个和下一个节点。此外,链表中还包含一个哨兵节点,可以让每个节点的prevnext都不为空,从而简化代码逻辑。

image-20240422190242784

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

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值