【算法社区】链表之合并两个有序链表、LRU缓存机制

简介: 字节跳动企业题库,链表系列,从出题频率最高刷到最低,题目有21. 合并两个有序链表 、146. LRU缓存机制

image.png

前言:📫 作者简介:小明java问道之路,专注于研究计算机底层,就职于金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的设计和架构📫

🏆 Java领域优质创作者、阿里云专家博主、华为云享专家🏆

🔥 如果此文还不错的话,还请👍关注点赞收藏三连支持👍一下博主哦

本文导读

字节跳动企业题库,链表系列,因为有leetcode会员能看到企业出题频率,那我们从出题频率最高刷到最低,题目有21. 合并两个有序链表 、146. LRU缓存机制


21. 合并两个有序链表

【简单】【题目描述】将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]

示例 2:输入:l1 = [], l2 = [] 输出:[]

示例 3:输入:l1 = [], l2 = [0] 输出:[0]

这道题是一到老生常谈的题目,对于熟悉链表的同学来说是秒杀的,我们分递归和迭代两种方法解题

【迭代、递归】图解:

image.png


代码详解,逐行注释:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /* 递归法:O(m+n),空间复杂度O(m+n) */
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 如果两个链表有一个为空,递归结束。
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        // 判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。
        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}


 146. LRU缓存机制

【中等】【题目描述】运用你所掌握的数据结构,设计和实现一个LRU(最近最少使用) 缓存机制

实现 LRUCache 类:

LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存

int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。

void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

示例:输入:

["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]

[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]

输出:[null, null, null, 1, null, -1, null, -1, 3, 4]

解释:

LRUCache lRUCache = new LRUCache(2);

lRUCache.put(1, 1); // 缓存是 {1=1}

lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}

lRUCache.get(1);    // 返回 1

lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}

lRUCache.get(2);    // 返回 -1 (未找到)

lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}

lRUCache.get(1);    // 返回 -1 (未找到)

lRUCache.get(3);    // 返回 3

lRUCache.get(4);    // 返回 4

【模拟法】图解:

image.png


代码详解,逐行注释:

publicclassLRUCache {
classDLinkedNode {
intkey;
intvalue;
DLinkedNodeprev;
DLinkedNodenext;
publicDLinkedNode() {}
publicDLinkedNode(int_key, int_value) {key=_key; value=_value;}
    }
privateMap<Integer, DLinkedNode>cache=newHashMap<Integer, DLinkedNode>();
privateintsize;
privateintcapacity;
privateDLinkedNodehead, tail;
publicLRUCache(intcapacity) {
this.size=0;
this.capacity=capacity;
// 使用伪头部和伪尾部节点head=newDLinkedNode();
tail=newDLinkedNode();
head.next=tail;
tail.prev=head;
    }
publicintget(intkey) {
DLinkedNodenode=cache.get(key);
if (node==null) {
return-1;
        }
// 如果 key 存在,先通过哈希表定位,再移到头部moveToHead(node);
returnnode.value;
    }
publicvoidput(intkey, intvalue) {
DLinkedNodenode=cache.get(key);
if (node==null) {
// 如果 key 不存在,创建一个新的节点DLinkedNodenewNode=newDLinkedNode(key, value);
// 添加进哈希表cache.put(key, newNode);
// 添加至双向链表的头部addToHead(newNode);
++size;
if (size>capacity) {
// 如果超出容量,删除双向链表的尾部节点DLinkedNodetail=removeTail();
// 删除哈希表中对应的项cache.remove(tail.key);
--size;
            }
        }
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部node.value=value;
moveToHead(node);
        }
    }
privatevoidaddToHead(DLinkedNodenode) {
node.prev=head;
node.next=head.next;
head.next.prev=node;
head.next=node;
    }
privatevoidremoveNode(DLinkedNodenode) {
node.prev.next=node.next;
node.next.prev=node.prev;
    }
privatevoidmoveToHead(DLinkedNodenode) {
removeNode(node);
addToHead(node);
    }
privateDLinkedNoderemoveTail() {
DLinkedNoderes=tail.prev;
removeNode(res);
returnres;
    }
}
/*** 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);*/
相关文章
|
1月前
|
消息中间件 人工智能 缓存
Go与Java Go和Java微观对比
本文对比了Go语言与Java在线程实现上的差异。Go通过Goroutines实现并发,使用`go`关键字启动;而Java则通过`Thread`类开启线程。两者在通信机制上也有所不同:Java依赖共享内存和同步机制,如`synchronized`、`Lock`及并发工具类,而Go采用CSP模型,通过Channel进行线程间通信。此外,文章还介绍了Go中使用Channel和互斥锁解决并发安全问题的示例。
138 0
|
4月前
|
Arthas 监控 Java
Arthas jvm(查看当前JVM的信息)
Arthas jvm(查看当前JVM的信息)
156 17
|
5月前
|
存储 弹性计算 资源调度
阿里云服务器收费模式对比:包年包月与按量付费的适用场景与选择参考
在我们购买阿里云服务器的时候,云服务器的收费模式主要有多种收费模式,其中包年包月和按量付费两种主流模式。对于准备在阿里云上部署应用的用户来说,选择合适的收费模式至关重要,因为它直接关系到成本控制和资源使用的灵活性。本文将对这两种收费模式做一个对比,以供参考和选择。
873 14
|
安全 Linux 网络安全
2023 年河北省职业院校信息安全管理与评估“(高职组) 技能大赛赛项规程
2023 年河北省职业院校信息安全管理与评估“(高职组) 技能大赛赛项规程
|
机器学习/深度学习 人工智能 算法
milvus源码编译
milvus源码编译
307 1
阿里云企航入选中国信通院《高质量数字化转型产品及服务全景图》
阿里云企航成为我国提供数字化转型产品和服务的优秀代表之一
1352 1
阿里云企航入选中国信通院《高质量数字化转型产品及服务全景图》
|
关系型数据库 Serverless 分布式数据库
PolarDB的Serverless能力与同类型产品的对比
【2月更文挑战第21天】PolarDB的Serverless能力与同类型产品的对比
129 2
|
iOS开发
iOS WKWebView 打开页面空白URL为空问题解决办法
iOS WKWebView 打开页面空白URL为空问题解决办法
916 0
|
存储 前端开发 Go
兼容并蓄广纳百川,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang复合容器类型的声明和使用EP04
书接上回,容器数据类型是指一种数据结构、或者抽象数据类型,其实例为其他类的对象。 或者说得更具体一点,它是以一种遵循特定访问规则的方法来存储对象。 容器的大小取决于其包含的基础数据对象(或数据元素)的个数。Go lang中常用的容器数据有数组、切片和集合。
兼容并蓄广纳百川,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang复合容器类型的声明和使用EP04
|
Perl
使用 awk 命令统计文本
下面只是在工作中可能会遇到的一个场景,所以记录下来,如果小伙伴有更合适的方式来统计计算,欢迎留言。
348 0