day2_greedyIntervalsLRU/LFU

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

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;// 排序后,第一个区间就是 xint 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;// 排序后,第一个区间就是 xint 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;// 排序后,第一个区间就是 xint 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]){ //当前的起始 < 上一个结束,表示相交// 找到最大的 endlast[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 中删除它的 keyint 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 对应的 freqincreaseFreq(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 肯定是 1this.minFreq = 1;}private void removeMinFreqKey() {// freq 最小的 key 列表LinkedHashSet<Integer> keyList = freqToKeys.get(this.minFreq);// 其中最先被插入的那个 key 就是该被淘汰的 keyint 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 对应的列表空了,移除这个 freqif (freqToKeys.get(freq).isEmpty()) {freqToKeys.remove(freq);// 如果这个 freq 恰好是 minFreq,更新 minFreqif (freq == this.minFreq) {this.minFreq++;}}}}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

摩菲Murphy显示器显示表 总线编程器维修PV780B

Murphy仪器维修包括&#xff1a;摩菲数字显示器&#xff1b;摩菲监视仪表&#xff1b;摩菲CAN总线控制器等维修 维修故障包括&#xff1a;黑屏、指示灯无显示&#xff0c;触摸屏上电无反应&#xff0c; 上电蓝屏、白屏&#xff0c;通电几分钟后屏幕变为蓝屏&#xff0c;主板故…

java中的oop(三)、构造器、javabean、uml类图、this、继承

!! 有get/set方法的情况基本就是说要搞个私有属性&#xff0c;不直接对外开放&#xff1b; 构造器 Person p new Person(); //其中的Person();就是构造器&#xff1b;---造对象&#xff1b;Constructor–建设者&#xff0c;建造者&#xff1b; 作用 搭配new 创建类的&…

DS:顺序表、单链表的相关OJ题训练(2)

欢迎各位来到 Harper.Lee 的学习世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu欢迎来后台找我哦&#xff01; 一、力扣--141. 环形链表 题目描述&#xff1a;给你一个链表的头节点 head &#xff0c;判断链表中是否有环。如果链表中有某个…

java数据结构之数组系统了解

1.数组介绍 数组就是一个存储数据的容器&#xff0c;容器的长度固定、存储元素的数据类型固定。 跟变量加以区分&#xff1a;变量也可以存储数据&#xff0c;但是只能存一个值。当要存的数据比较多的时候&#xff0c;用变量就不方便了。我们就可以使用数组来存储。 1.1数组…

嫁接打印的技术要点

所谓嫁接打印&#xff0c;是一种增减材混合制造的方式。它将已成形的模具零件当作基座&#xff0c;在此基础上“生长”出打印的零件。其中基座通常采用传统加工方式制造&#xff0c;而打印部分则使用专用的金属粉末&#xff0c;通过 3D 打印技术成型。 嫁接打印之所以备受欢迎&…

安全 | 开源入侵防御系统 Snort

目录 Snort 概要 入侵预防系统模式 数据包记录器和嗅探器模式 网络安全学习路线 &#xff08;2024最新整理&#xff09; 学习资料的推荐 1.视频教程 2.SRC技术文档&PDF书籍 3.大厂面试题 特别声明&#xff1a; Snort 概要 Snort 概要 是世界上最重要的开源入…

Python语言基础学习(上)

目录 一、常量和表达式 二、变量和类型 2.1 认识变量 2.2 定义变量 2.3 变量类型 1、整数 int 2、浮点数&#xff08;小数&#xff09;float 3、字符串 str 4、布尔类型 2.4 类型转换 三、注释 3.1 单行注释 3.2 文档注释&#xff08;或者多行注释&#xff09; …

MySQL 通过 systemd 启动时 hang 住了……

mysqld&#xff1a;哥&#xff0c;我起不来了…… 作者&#xff1a;贲绍华&#xff0c;爱可生研发中心工程师&#xff0c;负责项目的需求与维护工作。其他身份&#xff1a;柯基铲屎官。 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编…

谈基于ATTCK框架的攻击链溯源

引言 网络安全在当今数字化时代变得尤为关键&#xff0c;而MITRE公司开发的ATT&CK框架则成为了安全专业人员的重要工具。ATT&CK是一种广泛使用的攻击行为分类和描述框架。其目的在于提供一个共同的语言&#xff0c;使安全专业人员能够更好地理解攻击者的行为和目标&…

【NPM】Nginx Proxy Manager 一键申请 SSL 证书,自动续期,解决阿里云SSL免费证书每3个月失效问题

文章目录 1、NPM 简介2、实战Step 1&#xff1a;环境搭建 也可以看作者安装笔记 Step 2&#xff1a;创建容器 2.1 在系统任意位置创建一个文件夹&#xff0c;此文档以~/nginx-proxy-manager为例。2.2 创建docker-compose.yaml2.3 启动NPM服务 Step 3&#xff1a;配置反向代理3…

PyTorch 图像篇

计算机视觉技术是一门包括计算机科学与工程、神经生理学、物理学、信号处理、认知科学、应用数学与统计等多学科的综合性科学技术&#xff0c; 是人工智能的一个重要分支&#xff0c; 目前在智能安防、自动驾驶汽车、医疗保健、生成制造等领域具有重要的应用价值。 计算机视觉…

网络编程--tcp三次握手四次挥手

1、三次握手 &#xff08;1&#xff09;三次握手的详述 首先Client端发送连接请求报文&#xff0c;Server段接受连接后回复ACK报文&#xff0c;并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文&#xff0c;并分配资源&#xff0c;这样TCP连接就建立了。…