LeetCode 1146. 快照数组【哈希表+二分查找】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

实现支持下列接口的「快照数组」- SnapshotArray:

  • SnapshotArray(int length) - 初始化一个与指定长度相等的 类数组 的数据结构。初始时,每个元素都等于 0
  • void set(index, val) - 会将指定索引 index 处的元素设置为 val
  • int snap() - 获取该数组的快照,并返回快照的编号 snap_id(快照号是调用 snap() 的总次数减去 1)。
  • int get(index, snap_id) - 根据指定的 snap_id 选择快照,并返回该快照指定索引 index 的值。

示例:

输入:["SnapshotArray","set","snap","set","get"][[3],[0,5],[],[0,6],[0,0]]
输出:[null,null,0,null,5]
解释:
SnapshotArray snapshotArr = new SnapshotArray(3); // 初始化一个长度为 3 的快照数组
snapshotArr.set(0,5);  // 令 array[0] = 5
snapshotArr.snap();  // 获取快照,返回 snap_id = 0
snapshotArr.set(0,6);
snapshotArr.get(0,0);  // 获取 snap_id = 0 的快照中 array[0] 的值,返回 5

提示:

  • 1 <= length <= 50000
  • 题目最多进行50000 次 setsnap,和 get的调用 。
  • 0 <= index < length
  • 0 <= snap_id < 我们调用 snap() 的总次数
  • 0 <= val <= 10^9

解法 哈希表+二分查找

调用 s n a p ( ) snap() snap() 时,复制一份当前数组,作为「历史版本」。返回这是第几个历史版本(从 0 0 0 开始)。

调用 g e t ( i n d e x , s n a p I d ) get(index,snapId) get(index,snapId) 时,返回第 s n a p I d snapId snapId 个历史版本的下标为 index \textit{index} index 的元素值。

暴力?每次调用 s n a p ( ) snap() snap() ,就复制一份数组,可以吗?不行,最坏情况下,复制 50000 50000 50000 次长为 50000 50000 50000 的数组,会「超出内存限制」。

假设每调用一次 s e t set set ,就生成一个快照(复制一份数组)。仅仅是一个元素发生变化,就去复制整个数组,这太浪费了。

能否不复制数组呢?换个视角,调用 s e t ( index , val ) set(\textit{index}, \textit{val}) set(index,val) 时,不去修改数组,而是往下标为 index \textit{index} index 的历史修改记录末尾添加一条数据:此时的快照编号和 v a l val val 。有点像解决哈希冲突的拉链法

举例说明:

  • 在快照编号等于 2 2 2 时,调用 s e t ( 0 , 6 ) set(0, 6) set(0,6)
  • 在快照编号等于 3 3 3 时,调用 s e t ( 0 , 1 ) set(0,1) set(0,1)
  • 在快照编号等于 3 3 3 时,调用 s e t ( 0 , 7 ) set(0,7) set(0,7)
  • 在快照编号等于 5 5 5 时,调用 s e t ( 0 , 2 ) set(0,2) set(0,2)

这四次调用结束后,下标 0 0 0 的历史修改记录 history [ 0 ] = [ ( 2 , 6 ) , ( 3 , 1 ) , ( 3 , 7 ) , ( 5 , 2 ) ] \textit{history}[0] = [(2,6),(3,1),(3,7),(5,2)] history[0]=[(2,6),(3,1),(3,7),(5,2)] ,每个数对中的第一个数为调用 s e t set set 时的快照编号,第二个数为调用 s e t set set 时传入的 v a l val val 。注意历史修改记录中的快照编号是有序的

那么:

  • 调用 g e t ( 0 , 4 ) get(0,4) get(0,4) 。由于历史修改记录中的快照编号是有序的,我们可以在 h i s t o r y [ 0 ] history[0] history[0] 中二分查找快照编号 ≤ 4 \le 4 4 的最后一条修改记录,即 ( 3 , 7 ) (3,7) (3,7) 。修改记录中的 v a l = 7 val=7 val=7 就是答案。
  • 调用 g e t ( 0 , 1 ) get(0,1) get(0,1) 。在 h i s t o r y [ 0 ] history[0] history[0] 中,快照编号 ≤ 1 \le 1 1 的记录不存在,说明在快照编号 ≤ 1 ≤1 1 时,我们并没有修改下标 0 0 0 保存的元素,返回初始值 0 0 0

对于 s n a p snap snap,只需把当前快照编号加一(快照编号初始值为 0 0 0 ),返回加一前的快照编号。

class SnapshotArray:def __init__(self, length: int):self.cur_snap_id = 0self.history = defaultdict(list) # 每个index的历史修改记录都是listdef set(self, index: int, val: int) -> None:self.history[index].append((self.cur_snap_id, val))def snap(self) -> int:self.cur_snap_id += 1return self.cur_snap_id - 1def get(self, index: int, snap_id: int) -> int:# 找快照编号 <= snap_id 的最后一次修改记录# 等价于找快照编号 >= snap_id+1 的第一个修改记录,它的上一个就是答案j = bisect_left(self.history[index], (snap_id + 1, )) - 1return self.history[index][j][1] if j >= 0 else 0
class SnapshotArray {private final Map<Integer, List<int[]>> history = new HashMap<>();private int curSnapId; // 当前快照编号,初始值为0public SnapshotArray(int length) {}public void set(int index, int val) {history.computeIfAbsent(index, k -> new ArrayList<>()).add(new int[]{ curSnapId, val });}public int snap() {return curSnapId++;}public int get(int index, int snap_id) {if (!history.containsKey(index)) return 0;List<int[]> h = history.get(index);int j = search(h, snap_id);return j < 0 ? 0 : h.get(j)[1];}// 返回最大的下标i,满足 h[i][0]<=x// 如果不存在则返回-1private int search(List<int[]> h, int x) {// 开区间(left, right)int left = -1;int right = h.size();while (left + 1 < right) { // 区间不为空// 循环不变量// h[left][0] <= x// h[right][0] > xint mid = left + (right - left) / 2;if (h.get(mid)[0] <= x) {left = mid; // 区间缩小为(mid, right)} else {right = mid; // 区间缩小为(left, mid)}}// 根据循环不变量,此时 h[left][0]<=x 且 h[left+1][0] = h[right][0] > x// 所以left是最大的满足 h[left][0]<=x 的下标// 如果不存在,则left为其初始值-1return left;}
}
class SnapshotArray {
private:int cur_snap_id = 0;unordered_map<int, vector<pair<int, int>>> history; // 每个index的历史修改记录
public:SnapshotArray(int length) {}void set(int index, int val) {history[index].emplace_back(cur_snap_id, val);}int snap() {return cur_snap_id++;}int get(int index, int snap_id) {auto& h = history[index];// 找快照编号 <= snap_id 的最后一次修改记录// 等价于找快照编号 >= snap_id+1 的第一个修改记录,它的上一个就是答案int j = ranges::lower_bound(h, make_pair(snap_id + 1, 0)) - h.begin() - 1;return j >= 0 ? h[j].second : 0;}
};

复杂度分析:

  • 时间复杂度:初始化、 set s e t \texttt{set}set setset snap \texttt{snap} snap 均为 O ( 1 ) \mathcal{O}(1) O(1) get \texttt{get} get O ( log ⁡ q ) \mathcal{O}(\log q) O(logq) ,其中 q q q set \texttt{set} set 的调用次数。
  • 空间复杂度: O ( q ) \mathcal{O}(q) O(q)

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

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

相关文章

云原生Kubernetes: K8S 1.29版本 部署GitLab

目录 一、实验 1.环境 2.搭建NFS 3.K8S 1.29版本 部署Redis 4.K8S 1.29版本 部署Postgresql 5.K8S 1.29版本 部署GitLab 6.K8S 部署istio微服务 7.K8S 部署ingress应用路由 二、问题 1.K8S部署gitlab报错 2.gitlab创建失败 3.生成网关资源报错 4.安装istio 报错 …

APP自定义身份证相机(Android +iOS)

基本上同时兼容安卓和苹果的插件都需要付费&#xff0c;这里我找了2个好用的免费插件 1.仅支持安卓&#xff1a;自定义身份证相机&#xff08;支持蒙版自定义&#xff09;&#xff0c;内置蒙版&#xff0c;照片预览&#xff0c;身份证裁剪 - DCloud 插件市场、 2.支持iOS(已测…

打水问题(贪心算法)

题目&#xff1a;有n个人排队到r个水龙头去打水&#xff0c;他们装满水桶的时间t1、t2………tn为整数且各不相等&#xff0c;应如何安排他们的打水顺序才能使他们总共花费的时间最少&#xff1f;通过键盘输入排队打水的人数以及每人打水的时间和水龙头数&#xff0c;使用贪心算…

泰迪智能科技受邀参加2024年粤港澳大湾区产教融合技能人才培养联盟理事会会议

4月24日下午&#xff0c;2024年粤港澳大湾区产教融合技能人才培养联盟&#xff08;以下简称联盟&#xff09;理事会会议在白云区成功举办。 会议由广州市人力资源和社会保障局、广州市发展和改革委员会、广州市教育局、广州市工业和信息化局、广州市总工会等单位指导&#xff…

Python实现对规整的二维列表中每个子列表对应的值求和

目录 一、二维列表及其结构 二、对应位置元素求和的逻辑 三、代码实现 四、优化与改进 五、实际应用场景 六、扩展与变体 七、总结 在Python编程中&#xff0c;处理二维列表&#xff08;即列表的列表&#xff09;是一个常见的任务。有时候我们需要对二维列表中每个子列表…

多线程编程7——wait和notify、notifyAll

线程最大的问题就是抢占式执行&#xff0c;随机调度。可以通过一些API让线程主动阻塞&#xff0c;主动放弃CPU&#xff0c;从而控制线程之间的执行顺序。比如&#xff1a;join&#xff0c;sleep&#xff0c;wait和notify、notifyAll 前面章节已经介绍过 join 和 sleep了&#…

计算机网络-IPv6地址规范与分类

昨天学习了IPv6的基础概念&#xff0c;了解了IPv6的由来以及地址格式&#xff0c;今天继续学习下IPv6的地址分类与表示。 一、IPv6地址缩写规范 IPv6地址的长度为128 bit。一般用冒号分割为8段&#xff0c;每一段16 bit&#xff0c;每一段内用十六进制表示。 IPv6地址格式 那12…

3-成功初始化 Kubernetes 控制平面后如何操作

成功初始化 Kubernetes 控制平面后的一系列指示和建议&#xff0c;用于帮助你开始使用你的 Kubernetes 集群。下面是详细的解释和步骤&#xff1a; kubeadm init --apiserver-advertise-address 172.19.35.202 --image-repository registry.cn-hangzhou.aliyuncs.com/google_c…

MATLAB 基础使用教程

MATLAB 的基本使用主要包括如下几个方面&#xff1a;熟悉 MATLAB 环境&#xff0c;数据的输入输出&#xff0c;基本的数学运算&#xff0c;图形绘制&#xff0c;编程等。以下通过一些例子进行简要说明。 1. 熟悉 MATLAB 环境 启动 MATLAB 后&#xff0c;你会看到 MATLAB 的桌面…

【kettle004】kettle访问本地MySQL数据库并处理数据至execl文件

一直以来想写下基于kettle的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 熟悉、梳理、总结下MySQL关系数据库相关知识体系 kettle访问MySQL数据库并处理数据至execl文件…

Linux 权限提升 - 信息收集 清单

这个清单主要使用于内网渗透的其中一个环节&#xff0c;信息收集此环节涉及后续是否有高效的攻击成绩&#xff0c;通过某些手段和技巧&#xff0c;可获取更多的彩蛋&#xff01;&#x1f386; 攻击者可以发现目标系统的弱点和漏洞&#xff0c;包括但不限于&#xff1a; 发现潜…

代码随想录算法训练营Day25 | 216.组合总和III、17.电话号码的字母组合 | Python | 个人记录向

本文目录 216.组合总和III做题看文章 17.电话号码的字母组合做题看文章 以往忽略的知识点小结个人体会 216.组合总和III 代码随想录&#xff1a;216.组合总和III Leetcode&#xff1a;216.组合总和III 做题 参照着Day24中77.组合的结构&#xff0c;调试后AC了&#xff0c;代…

详解SPI、I2C、UART、I2S、GPIO、SDIO、CAN

总线,总线,总要陷进里面。这世界上的信号都一样,但是总线却成千上万,让人头疼。 总的来说,总线有三种:内部总线、系统总线和外部总线。内部总线是微机内部各外围芯片与处理器之间的总线,用于芯片一级的互连;而系统总线是微机中各插件板与系统板之间的总线,用于插件板一…

Android 生成二维码

一、生成二维码工具类封装 1、二维码库 // 二维码implementation com.journeyapps:zxing-android-embedded:4.3.0 2、工具类 /*** 二维码* 处理工具*/public class QRCodeDealUtils {/*** param content 字符串内容* param size 位图宽&高(单位:px)* param log…

接口的构成

目录 接口 一、URL 二、请求方法 三、请求报文&#xff08;request message&#xff09; 3.1请求行 3.2请求头 3.3 请求体 四、响应报文&#xff08;response message&#xff09; 4.1响应行 4.2响应头 4.3响应体 接口 接口就是API&#xff0c;是程序开发的函数和方…

为什么要进行人脸识别?

人脸识别技术被广泛应用于各种场景和行业&#xff0c;其主要目的包括但不限于以下几点&#xff1a; 1. **安全验证**&#xff1a;人脸识别可以用作身份验证的一种方式&#xff0c;确保只有授权人员才能进入特定的区域或访问敏感信息。例如&#xff0c;用于解锁手机或电脑、进入…

Linux---为什么会有粘滞位?

在前面已经讲过目录的rwx权限&#xff1a; 可读权限(r): 如果目录没有可读权限, 则无法用ls等命令查看目录中的文件内容. 有可写权限(w):如果目录没有可写权限&#xff0c;则无法在目录中创建文件, 也无法在目录中删除文件.可执行权限(x): 如果目录没有可执行权限, 则无法cd到…

MOUNT windows到本机

mount -t cifs -o username"Administrator",password"123456789",vers2.0,rw,uid0,gid0 //192.168.10.68/home /home/windows/windowsHome 其中&#xff1a; username"Administrator" 为Windows用户的用户名 password"123456789" …

D-Wave 推出快速退火功能,扩大量子计算性能增益

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 文丨浪味仙 排版丨沛贤 深度好文&#xff1a;1400字丨6分钟阅读 摘要&#xff1a;量子计算公司 D-Wave 宣布在其 Leap™ 实时量子云服务中的所有量子处理单元 (QPU) 上推出新的快速退火功能。…

Java 网络编程之TCP(五):分析服务端注册OP_WRITE写数据的各种场景(二)

接上文 二、注册OP_WRITE写数据 服务端代码&#xff1a; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.S…