day2_greedyIntervalsLRU/LFU

news2024/11/19 8:22:08

二、贪心算法之区间调度问题

0.计算一个区间集合中无重复的区间的最大数量(模板)
public int intervalSchedule(int[][] intvs) {
    if (intvs.length == 0) return 0;
    // 按 end 升序排序
    Arrays.sort(intvs, (a, b) -> Integer.compare(a[1], b[1]));
    // 至少有一个区间不相交
    int count = 1;
    // 排序后,第一个区间就是 x
    int x_end = intvs[0][1];
    for (int[] interval : intvs) {
        int start = interval[0];
        if (start >= x_end) {
            // 找到下一个选择的区间了
            count++;
            x_end = interval[1];
        }
    }
    return count;
}

1.435无重叠区间
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        int n = intervals.length;
        if (n == 0) return 0;
            // 按 end 升序排序
            Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
            // 至少有一个区间不相交
            int count = 1;
            // 排序后,第一个区间就是 x
            int x_end = intervals[0][1];
            for (int[] interval : intervals) {
                int start = interval[0];
                if (start >= x_end) {
                    // 找到下一个选择的区间了
                    count++;
                    x_end = interval[1];
                }
            }
            //count为最多的互不相交的区间,n - count就为最少的需要去除的区间数量
            count = n - count;
            return count;
            }
}
2.452.用最少的箭射爆气球

与模板不同点是在 intervalSchedule 算法中,如果两个区间的边界触碰,不算重叠;而按照这道题目的描述,箭头如果碰到气球的边界气球也会爆炸,所以说相当于区间的边界触碰也算重叠

将不重叠区间的条件 从 >= 改为 > 即可

class Solution {
    public int findMinArrowShots(int[][] points) {
    if (points.length == 0) return 0;
    // 按 end 升序排序
    Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
    // 至少有一个区间不相交
    int count = 1;
    // 排序后,第一个区间就是 x
    int x_end = points[0][1];
    for (int[] interval : points) {
        int start = interval[0];
        if (start > x_end) {
            // 找到下一个选择的区间了
            count++;
            x_end = interval[1];
        }
    }
    return count;
    }
}

三、一个方法解决三道区间题

在这里插入图片描述

1.区间覆盖问题

1288删除被覆盖区间,返回剩余区间的数量

int removeCoveredIntervals(int[][] intvs) {
    // 按照起点升序排列,起点相同时降序排列
    Arrays.sort(intvs, (a, b) -> {
        if (a[0] == b[0]) {
            return b[1] - a[1];
        }
        return a[0] - b[0]; 
    });

    // 记录合并区间的起点和终点
    int left = intvs[0][0];
    int right = intvs[0][1];
    
    int res = 0;
    for (int i = 1; i < intvs.length; i++) {
        int[] intv = intvs[i];
        // 情况一,找到覆盖区间
        if (left <= intv[0] && right >= intv[1]) {
            res++;
        }
        // 情况二,找到相交区间,合并
        if (right >= intv[0] && right <= intv[1]) {
            right = intv[1];
        }
        // 情况三,完全不相交,更新起点和终点
        if (right < intv[0]) {
            left = intv[0];
            right = intv[1];
        }
    }
    //总数量减去合并后剩余的区间数量就是被删除的区间数量
    return intvs.length - res;
}

2.区间合并问题
class Solution {
    public int[][] merge(int[][] intervals) {
    if(intervals == null || intervals.length == 0) return new int[][]{};
    // 按区间的 start 升序排列
    Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
    List<int[]> res = new ArrayList<>();
    //将第一个元素加入到res中
    res.add(intervals[0]);
    
    // 注意从1开始
    for(int i=1; i<intervals.length; i++){
        int[] curr = intervals[i];
        // res 中最后一个元素的引用
        int[] last = res.get(res.size()-1);
        if(curr[0] <= last[1]){ //当前的起始 < 上一个结束,表示相交
            // 找到最大的 end
            last[1] = Math.max(last[1], curr[1]);
        } else{ //否则不相交
            // 处理下一个待合并区间
            res.add(curr);
        }
    }
    return res.toArray(new int[res.size()][]);
    }
}
3.区间交集问题

三个关键点

  1. 两区间相交的条件
  2. 获取相交区间的左端点和右端点
  3. 两个指针什么情况下移动
class Solution {
    public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
        int i = 0, j = 0;
        List<int[]> res = new ArrayList<>();
        while (i < firstList.length && j < secondList.length) {
        //a1,b1 是 两个区间列表的左端点,a2,b2是对应的右端点
            int a1 = firstList[i][0], a2 = firstList[i][1];
            int b1 = secondList[j][0], b2 = secondList[j][1];
            // 两个区间存在交集,使用没有交集的对立条件推出存在交集的条件
            //if b2 < a1 or a2 < b1:  [a1,a2] 和 [b1,b2] 无交集
            if (b2 >= a1 && a2 >= b1) {
                // 计算出交集,加入 res; 交集是左端点的最大值和右端点的最小值组成的
                res.add(new int[]{Math.max(a1, b1), Math.min(a2, b2)});
            }
            // 指针前进,取决于右端点的大小,谁小谁就向后遍历
            if (b2 < a2) {
                j++;
            } else {
                i++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

四、LRU缓存 && LFU缓存

这个算是我老朋友了,我窥觊它得一个月了,但实在太难,直到遇见labuladong,学的更轻松了,也算是激发了我学习热情,感谢dong哥

LRU 算法的核心数据结构是使用哈希链表 LinkedHashMap,首先借助链表的有序性使得链表元素维持插入顺序,同时借助哈希映射的快速访问能力使得我们可以在 O(1) 时间访问链表的任意元素

1.LRU缓存
  1. 使用哈希双端链表(Java中对应的是LinkedHashMap)

  2. 创建Node单个节点 -> 组成DoubleList双端链表

  3. 因为同时维护map的双端链表,需要抽象出来几个方法在对元素操作时同时操作map和DoubleList,防止漏掉操作,比如说删除某个 key 时,在 cache 中删除了对应的 Node,但是却忘记在 map 中删除 key

    解决这种问题的有效方法是:在这两种数据结构之上提供一层抽象 API

    说的有点玄幻,实际上很简单,就是尽量让 ==LRU 的主方法 getput 避免直接操作 mapcache==的细节。我们可以先实现下面几个函数:

  4. get逻辑比较简单,这里阐述一下put的逻辑

    1. 若key已经存在,那么修改key对应的value
    2. 若key不存在,需要插入key
      1. 当容量满的时候,淘汰最久未使用的key
      2. 当容量没有满,直接执行c
    3. 插入key和val为最近使用的数据
class LRUCache {

    // key -> Node(key, val)
    private HashMap<Integer, Node> map;
    // Node(k1, v1) <-> Node(k2, v2)...
    private DoubleList cache;
    // 最大容量
    private int cap;
    
    public LRUCache(int capacity) {
        this.cap = capacity;
        map = new HashMap<>();
        cache = new DoubleList();
    }


    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; }

}

 /* 将某个 key 提升为最近使用的 */
    private void makeRecently(int key) {
        Node x = map.get(key);
        // 先从链表中删除这个节点
        cache.remove(x);
        // 重新插到队尾
        cache.addLast(x);
    }

    /* 添加最近使用的元素 */
    private void addRecently(int key, int val) {
        Node x = new Node(key, val);
        // 链表尾部就是最近使用的元素
        cache.addLast(x);
        // 别忘了在 map 中添加 key 的映射
        map.put(key, x);
    }

    /* 删除某一个 key */
    private void deleteKey(int key) {
        Node x = map.get(key);
        // 从链表中删除
        cache.remove(x);
        // 从 map 中删除
        map.remove(key);
    }

    /* 删除最久未使用的元素 */
    private void removeLeastRecently() {
        // 链表头部的第一个元素就是最久未使用的
        Node deletedNode = cache.removeFirst();
        // 同时别忘了从 map 中删除它的 key
        int deletedKey = deletedNode.key;
        map.remove(deletedKey);
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        // 将该数据提升为最近使用的
        makeRecently(key);
        return map.get(key).val;
    }

    public void put(int key, int val) {
        if (map.containsKey(key)) {
            // 删除旧的数据
            deleteKey(key);
            // 新插入的数据为最近使用的数据
            addRecently(key, val);
            return;
        }
        
        if (cap == cache.size()) {
            // 删除最久未使用的元素
            removeLeastRecently();
        }
        // 添加为最近使用的元素
        addRecently(key, val);
    }

}

/**
 * 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);
 */
2.LFU缓存
class LFUCache {
    // key 到 val 的映射,我们后文称为 KV 表
    HashMap<Integer, Integer> keyToVal;
    // key 到 freq 的映射,我们后文称为 KF 表
    HashMap<Integer, Integer> keyToFreq;
    // freq 到 key 列表的映射,我们后文称为 FK 表
    HashMap<Integer, LinkedHashSet<Integer>> freqToKeys;
    // 记录最小的频次
    int minFreq;
    // 记录 LFU 缓存的最大容量
    int cap;

    public LFUCache(int capacity) {
        keyToVal = new HashMap<>();
        keyToFreq = new HashMap<>();
        freqToKeys = new HashMap<>();
        this.cap = capacity;
        this.minFreq = 0;
    }

    public int get(int key) {
        if (!keyToVal.containsKey(key)) {
            return -1;
        }
        // 增加 key 对应的 freq
        increaseFreq(key);
        return keyToVal.get(key);
    }

    public void put(int key, int val) {
        if (this.cap <= 0) return;

        /* 若 key 已存在,修改对应的 val 即可 */
        if (keyToVal.containsKey(key)) {
            keyToVal.put(key, val);
            // key 对应的 freq 加一
            increaseFreq(key);
            return;
        }

        /* key 不存在,需要插入 */
        /* 容量已满的话需要淘汰一个 freq 最小的 key */
        if (this.cap <= keyToVal.size()) {
            removeMinFreqKey();
        }

        /* 插入 key 和 val,对应的 freq 为 1 */
        // 插入 KV 表
        keyToVal.put(key, val);
        // 插入 KF 表
        keyToFreq.put(key, 1);
        // 插入 FK 表
        freqToKeys.putIfAbsent(1, new LinkedHashSet<>());
        freqToKeys.get(1).add(key);
        // 插入新 key 后最小的 freq 肯定是 1
        this.minFreq = 1;
    }
    
    private void removeMinFreqKey() {
        // freq 最小的 key 列表
        LinkedHashSet<Integer> keyList = freqToKeys.get(this.minFreq);
        // 其中最先被插入的那个 key 就是该被淘汰的 key
        int deletedKey = keyList.iterator().next();
        /* 更新 FK 表 */
        keyList.remove(deletedKey);
        if (keyList.isEmpty()) {
            freqToKeys.remove(this.minFreq);
            // 问:这里需要更新 minFreq 的值吗?
        }
        /* 更新 KV 表 */
        keyToVal.remove(deletedKey);
        /* 更新 KF 表 */
        keyToFreq.remove(deletedKey);
    }
    
    private void increaseFreq(int key) {
        int freq = keyToFreq.get(key);
        /* 更新 KF 表 */
        keyToFreq.put(key, freq + 1);
        /* 更新 FK 表 */
        // 将 key 从 freq 对应的列表中删除
        freqToKeys.get(freq).remove(key);
        // 将 key 加入 freq + 1 对应的列表中
        freqToKeys.putIfAbsent(freq + 1, new LinkedHashSet<>());
        freqToKeys.get(freq + 1).add(key);
        // 如果 freq 对应的列表空了,移除这个 freq
        if (freqToKeys.get(freq).isEmpty()) {
            freqToKeys.remove(freq);
            // 如果这个 freq 恰好是 minFreq,更新 minFreq
            if (freq == this.minFreq) {
                this.minFreq++;
            }
        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1662061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web3 Tools - Base58

Base58编码 Base58编码是一种用于表示数字的非常见的编码方法。它通常用于加密货币领域&#xff0c;例如比特币和其他加密货币的地址表示。 什么是Base58编码&#xff1f; Base58编码是一种将数字转换为人类可读形式的编码方法。与常见的Base64编码不同&#xff0c;Base58编码…

Docker常用镜像安装

1. mysql 1.1 安装 获取镜像 docker pull mysql:8.0.30创建文件挂载目录 创建容器并运行 docker run -p 3306:3306 --name mysql8 \ -v /home/docker/mysql8/log:/var/log/mysql \ -v /home/docker/mysql8/data:/var/lib/mysql \ -v /home/docker/mysql8/mysql-files:/va…

鸿蒙OpenHarmony开发板解析:【特性配置规则】

特性 特性配置规则 下面介绍feature的声明、定义以及使用方法。 feature的声明 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 在部件的bundle.json文件中通过feature_list来声明部件的feature列…

『大模型笔记』Google CEO Sundar Pichai(桑达尔·皮查伊)谈人工智能的未来!

Google CEO Sundar Pichai(桑达尔皮查伊)谈人工智能的未来! 文章目录 一. Google CEO谈人工智能的未来总结摘要观点时间线二. 参考文献中文字幕视频链接,欢迎关注我的xhs账号:Google CEO 皮查伊谈人工智能的未来! 一. Google CEO谈人工智能的未来

VTK 数据类型:规则网格

VTK 数据类型&#xff1a;规则网格 VTK 数据类型&#xff1a;规则网格分类三种规则网格需要的设置实例 VTK 数据类型&#xff1a;规则网格 分类 VTK 有 3 种规则网格&#xff1a; vtkImageData&#xff1a;几何结构和拓扑结构都是规则的。vtkRectilinearGrid&#xff1a;几何…

大语言模型的后处理

后处理的输入 常规意义上的大模型处理流程 import torch from transformers import LlamaForCausalLM, LlamaTokenizer# 加载模型和tokenizer model LlamaForCausalLM.from_pretrained("decapoda-research/llama-7b-hf") tokenizer LlamaTokenizer.from_pretrain…

《Python编程从入门到实践》day24

# 昨日知识点学习 创建外星人从一个到一行 # 主程序snipdef _create_fleet(self):"""创建外星人群"""# 创建一个外星人并计算一行可容纳多少个外星人# 外星人的间距为外星人的宽度alien Alien(self)alien_width alien.rect.widthavailable_sp…

使用Vue调用ColaAI Plus大模型,实现聊天(简陋版)

首先去百度文心注册申请自己的api 官网地址&#xff1a;LuckyCola 注册点开个人中心 查看这个文档自己申请一个ColaAI Plus定制增强大模型API | LuckyColahttps://luckycola.com.cn/public/docs/shares/api/colaAi.html来到vue的页面 写个样式 <template><Header …

Springboot+Vue项目-基于Java+MySQL的宠物商城网站系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

调试记录 CPU PCIE 找不到设备,AC 耦合电容的问题

1. 问题 现象&#xff1a; 1. 国产CPU的主板&#xff0c;主板内的PCIE 设备找的到&#xff0c;但是另一块板子上连接的PCIE 设备找不到。 2. 排查问题在哪里的计划 1. 检查原理图先排除信号定义的问题&#xff0c; TXRX是否反接。 2. 示波器检查PCIE 的时钟频率是否正确。 3. …

图神经网络(GNNs)在时间序列分析中的应用

时间序列数据是记录动态系统测量的主要数据类型&#xff0c;由物理传感器和在线过程&#xff08;虚拟传感器&#xff09;大量生成。时间序列分析对于解锁可用数据中隐含的丰富信息至关重要。随着图神经网络&#xff08;GNNs&#xff09;的最近进展&#xff0c;基于GNN的方法在时…

[嵌入式系统-73]:RT-Thread-快速上手:如何选择RT Thread的版本?

目录 如何选择合适的 RT-Thread 版本进行开发&#xff1f; RT-Thread 分支与版本介绍 如何选择 发布版本&#xff08;GitHub releases&#xff09; 开发分支&#xff08;GitHub master 主分支&#xff09; 长期支持分支&#xff08;GitHub lts-v3.1.x 分支&#xff09; …

面向对象设计之套路——设计模式

1、总则 面向对象的分析设计编程思想&#xff0c;通过封装、继承、多态把程序的耦合度降低&#xff0c;用设计模式使得程序更加灵活&#xff0c;容易修改&#xff0c;并且易于复用。 让业务逻辑与界面逻辑分开&#xff0c;让它们的耦合度下降&#xff0c;只有分离&#xff0c;…

JAVA使用Apache POI动态导出Word文档

文章目录 一、文章背景二、实现步骤2.1 需要的依赖2.2 创建模板2.3 书写java类2.3.1 模板目录2.3.2 Controller类2.3.2 工具类 2.4 测试2.4.1 浏览器请求接口2.4.2 下载word 三、注意事项四、其他导出word实现方式 一、文章背景 基于Freemarker模版动态生成并导出word文档存在弊…

西米支付:数字藏品元宇宙的介绍与骗局套路解析

一、什么是元宇宙&#xff1f; 元宇宙是一个集体虚拟共享空间&#xff0c;由虚拟增强的物理现实和物理持久的虚拟空间融合而创造&#xff0c;包括所有虚拟世界、增强现实和互联网的总和。简单地说&#xff0c;元宇宙是Web3.0时期的数字世界。 这类新兴概念被非法分子包装后&am…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

【树莓派4B】如何用树莓派的串口发送数据给单片机

文章目录 查看路由器中的树莓派IProot连接打开vnc远程桌面服务打开win的远程桌面软件输入IP和端口串口发送数据硬件连接树莓派发送 查看路由器中的树莓派IP root连接 打开vnc远程桌面服务 vncserver :1打开win的远程桌面软件 输入IP和端口 192.168.3.33:1输入密码qwer1234后点…

draw.io-24-windows-no-installer.exe是一个自解压文件

draw.io-24-windows-no-installer.exe是一个自解压文件&#xff0c;每次启动都会执行解压操作&#xff0c;在以下两个位置产生程序主体文件draw.io.exe&#xff0c;大约170M&#xff0c;程序关闭后再删除解压后的文件&#xff0c;给硬盘增加不必要的读写操作。 C:\Users\AAA\Ap…

工作中使用Optional过滤出符合条件的数据

工作中使用Optional获取非空对象的属性 实体类Optional对非空对象的处理满足过滤条件返回的值不满足条件返回的值 实体类 package po;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.io.Serializable;Data AllArgsConst…

从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离

本文为从零开始写 Docker 系列第十四篇&#xff0c;实现容器间的 rootfs 隔离&#xff0c;使得多个容器间互不影响。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原理&#xff1a;…