【每日算法题3.4】模拟LRU缓存

模拟LRU缓存

链表显然是支持在任意位置快速插入和删除的,改改指针就行。只不过传统的链表无法按照索引快速访问某一个位置的元素,而这里借助哈希表,可以通过 key 快速映射到任意一个链表节点,然后进行插入和删除
这里一定要用双链表,因为需要有删除和增加节点的操作,需要操作其前驱节点的指针,而双向链表才能支持直接查找前驱,保证操作的时间复杂度 O(1)

  • 【链表与顺序表(数组)的区别】
    顺序表:
    1、优点:顺序表空间连续,支持随机访问;
    2、缺点:
    中间或前面部分的插入删除操作时间复杂度 O(N)
    增容的代价比较大
    (方便查找,不方便插入和删除)
    链表:
    1、优点:
    任意位置插入删除的时间复杂度为 O(1)
    没有增容问题,插入一个开辟一个空间
    2、缺点:以节点为单位存储,不支持随机访问
    (不方便查找,方便插入删除)

**解题思路:**确定要使用的数据结构:双链表、哈希表,然后模拟LRU机制。

package Solution2;

import java.util.*;

// 链表节点
class Node {
    public int key, val;
    public Node next, prev;
    public Node(int k, int v) {
        this.key = k;
        this.val = v;
    }
}

// 构造双链表
class DoubleList {
    // 头尾虚节点
    private Node head, tail;
    // 链表元素数
    private int size;

    public DoubleList() {
        // 初始化双向链表的数据
        head = new Node(0, 0); // 越接近表头则是最久未被使用的
        tail = new Node(0, 0); // 每次把最新的节点插入到表尾之前
        head.next = tail;
        tail.prev = head;
        size = 0;
    }

    // 在链表尾部添加节点 x,时间 O(1)
    public void addLast(Node x) {
        x.prev = tail.prev;
        x.next = tail;
        tail.prev.next = x;
        tail.prev = x;
        size++;
    }

    // 删除链表中的 x 节点(x 一定存在)
    // 由于是双链表且给的是目标 Node 节点,时间 O(1)
    public void remove(Node x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
        size--;
    }

    // 删除链表中第一个节点,并返回该节点,时间 O(1)
    public Node removeFirst() {
        if (head.next == tail)
            return null;
        Node first = head.next;
        remove(first);
        return first;
    }

    // 返回链表长度,时间 O(1)
    public int size() { return size; }

}

public class Solution2 {
    /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public static int[] LRU(int[][] operators, int k) {
        ArrayList<Integer> ans = new ArrayList<Integer>();

        // HashMap是为了提高查找效率 O(1),链表的查找效率为O(N)
        HashMap<Integer, Node> find_map = new HashMap<Integer, Node>();
        DoubleList list = new DoubleList();
        for(int i=0; i < operators.length; i++){
            if(operators[i][0]==1){
                // 若链表大小超过K
                if(list.size() >= k){
                    // 删除表头最久未被使用的节点
                    find_map.remove(list.removeFirst().key);
                }
                // 更新双链表和HashMap
                Node node = new Node(operators[i][1],operators[i][2]);
                list.addLast(node);
                find_map.put(operators[i][1], node);
            } else{// 进行查找操作
                Node node = find_map.get(operators[i][1]);
                if (node==null){
                    ans.add(-1);
                    continue;
                }
                ans.add(node.val);
                // 更新链表
                list.remove(node);
                list.addLast(node);
                find_map.put(node.key, node);
            }
        }
        int[]ans_ = new int[ans.size()];
        for(int i=0;i<ans.size();i++){
            ans_[i] = ans.get(i);
        }
        return ans_;
    }

    public static void main(String[] args) {
        int [][]operators = {{1,1,1},{1,2,2},{1,3,2},{2,1},{1,4,4},{2,2}};
        int k = 3;
        LRU(operators, k);
    }
}

寻找最小的K个数

解法一
要求一个序列中最小的k个数,按照惯有的思维方式,则是先对这个序列从小到大排序,然后输出前面的最小的k个数。

至于选取什么的排序方法,我想你可能会第一时间想到快速排序(我们知道,快速排序平均所费时间为n*logn),然后再遍历序列中前k个元素输出即可。因此,总的时间复杂度:O(n * log n)+O(k)=O(n * log n)。

解法二
咱们再进一步想想,题目没有要求最小的k个数有序,也没要求最后n-k个数有序。既然如此,就没有必要对所有元素进行排序。这时,咱们想到了用选择或交换排序,即:

1、遍历n个数,把最先遍历到的k个数存入到大小为k的数组中,假设它们即是最小的k个数;
2、对这k个数,利用选择或交换排序找到这k个元素中的最大值kmax(找最大值需要遍历这k个数,时间复杂度为O(k));
3、继续遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与kmax比较:如果x < kmax ,用x替换kmax,并回到第二步重新找出k个元素的数组中最大元素kmax‘;如果x >= kmax,则继续遍历不更新数组。

每次遍历,更新或不更新数组的所用的时间为O(k)或O(0)。故整趟下来,时间复杂度为nO(k)=O(nk)。

解法三
更好的办法是维护容量为k的最大堆,原理跟解法二的方法相似:

1、用容量为k的最大堆存储最先遍历到的k个数,同样假设它们即是最小的k个数;
2、堆中元素是有序的,令k1<k2<…<kmax(kmax设为最大堆中的最大元素)
3、遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与堆顶元素kmax比较:如果x < kmax,用x替换kmax,然后更新堆(用时logk);否则不更新堆。
这样下来,总的时间复杂度:O(k+(n-k)logk)=O(nlogk)。此方法得益于堆中进行查找和更新的时间复杂度均为:O(logk)(若使用解法二:在数组中找出最大元素,时间复杂度:O(k))。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值