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://xiahunao.cn/news/3004047.html

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

相关文章

JUC线程

进程和线程&#xff1a; 进程&#xff08;Process&#xff09;是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配的基本单位&#xff0c;是操作系统结构的基础。 线程&#xff08;英语&#xff1a;thread&#xff09;是操作系统能够进行运算调度…

五大开放式耳机推荐,选对耳机让运动更带感!

看似精彩的户外运动经历背后&#xff0c;其实是枯燥的体能运动和训练&#xff0c;以及独自长途和长时间旅行伴随的孤独感&#xff0c;而排解这些不良情绪的最佳方式就是音乐。如果你希望在运动、舒适、安全和音质之间获得一个最佳平衡&#xff0c;那相比入耳式耳机&#xff0c;…

基于SSM的个人博客系统(四)

目录 5.3 博客类别管理模块 5.3.1 添加博客类别 5.3.2 修改博客类别 5.3.3 删除博客类别 5.3.4 显示博客类别 5.4 评论管理模块 5.4.1 审核评论 5.4.2 删除评论 前面内容请移步 基于SSM的个人博客系统&#xff08;三&#xff09; 个人博客系统的设计与实现免费源码…

Java对接高德api搜索POI 2.0 关键字搜索

目录 一、注册账号 二、搜索小demo 1.首先要引入依赖 2. 然后查看打印结果即可 三、搜索接口代码 1.引入依赖 2.yml配置 2.Controller 3.静态工具类 四、运行测试 一、注册账号 高德开放平台 | 高德地图API 注册高德开发者&#xff1b;去控制台创建应用&#xff…

带状疱疹科普

什么是带状疱疹 早期疱疹 4天后的疱疹(第三天开始用药) 每种药物的作用 原理 我使用的药物 - 注意事项: 吃药前需要做抽血检查肾功能,肾功能不好不建议吃该药物. 有条件的建议去医院皮肤科,此文章仅仅科普

【题解 | 思维】三元组中心问题

三元组中心问题 注意点&#xff1a; 同一个位置的元素&#xff0c;不管以它为中心能组成多少个三元组&#xff0c;只记一个不同索引位置的相同元素&#xff0c;算多个中心元素。 常规暴力 import java.util.Scanner;public class Main {public static void main(String[] args…

实时监控视频拼接系统:功能和拼接参数介绍

目录 一、实时视频拼接系统介绍 &#xff08;一&#xff09;实时视频拼接的定义 &#xff08;二&#xff09;主要功能 1、视频拼接 2、拼接形式选择 3、前端选择 4、拼接展示 5、数据处理效率提升 6、任务管理 &#xff08;三&#xff09;实时拼接效果 二、拼接需要…

YOLOv9/YOLOv8算法改进【NO.126】YOLOv9的RepNCSPELAN进行二次创新

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 首推…

springBootAdmin监控

简介 用于对 Spring Boot 应用的管理和监控。可以用来监控服务是否健康、是否在线、以及一些jvm数据等等 Spring Boot Admin 分为服务端(spring-boot-admin-server)和客户端(spring-boot-admin-client)&#xff0c;服务端和客户端之间采用 http 通讯方式实现数据交互&#xf…

【IDEA】2023版IDEA安装破解教程

2023版IDEA安装破解教程 第一步&#xff1a;IDEA的卸载 这里以Windows11系统为例&#xff0c;首先我们打开控制面板&#xff0c;点击程序&#xff0c;找到自己的IDEA&#xff0c;双击卸载。&#xff08;或者可以直接找到idea所在文件位置&#xff0c;直接delete文件夹&#x…

Java | Leetcode Java题解之第59题螺旋矩阵II

题目&#xff1a; 题解&#xff1a; class Solution {public int[][] generateMatrix(int n) {int num 1;int[][] matrix new int[n][n];int left 0, right n - 1, top 0, bottom n - 1;while (left < right && top < bottom) {for (int column left; co…

图神经网络入门与实战:从图嵌入(GE)到图神经网络(GNN)

目录 一. 图的基本概念(Graph) 1.1 图的定义 1.2 图表示的基本概念 1.3 图的应用场景 1.4 图的分类 二. 图嵌入(Graph Embedding) 2.1 图嵌入的基本概念 2.2 图嵌入方法分类 2.3 图嵌入和图神经网络的区别 三. 图神经网络(Graph Neural Network) 3.1 图神经网络的基…

docker 集群管理实战mesos+zookeeper+marathon(一)

一 实验环境 1.1 系统版本&#xff0c;本实验使用cnetos7.9版本镜像 1.2 准备5台虚拟机&#xff0c;其中3台master&#xff0c;两台slave&#xff0c;使用克隆的方式 1.3 使用远程连接工具登录 1.4 修改主机名 1.5 设置域名映射 每个虚拟机都配置一下&#xff0c;这里就演示一…

Golang | Leetcode Golang题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; func minPathSum(grid [][]int) int {if len(grid) 0 || len(grid[0]) 0 {return 0}rows, columns : len(grid), len(grid[0])dp : make([][]int, rows)for i : 0; i < len(dp); i {dp[i] make([]int, columns)}dp[0][0] grid[0][0]…

双塔模型模型结构、样本选择、训练方式、线上服务、模型更新

召回模型目的是快速选取用户可能感兴趣的物品&#xff0c;凡事用户可能感兴趣的都取回来 然后交给后续排序模型逐一甄别。 双塔模型结构 不止能使用id特征&#xff08;能使用id之外的其他特征&#xff09;&#xff0c;用户侧能用画像等其他特征&#xff0c;包括离散特征和连续…

自定义 Dockerfile 构建 PostgreSQL 15 编译版 Docker 镜像

BG 前几日 Sean 老师发布了一篇文章 – PostgreSQL安装(一): 再简单点儿&#xff0c;用Docker?, 介绍如何快速安装启动 PostgreSQL 数据库。 本文再稍微延伸一点&#xff0c;介绍一下如何自定义 Dockerfile&#xff0c;加入自己想要预制的参数&#xff0c;构建一个自定义的 …

【经典算法】LeetCode112. 路径总和(Java/C/Python3/Go实现含注释说明,Easy)

作者主页&#xff1a; &#x1f517;进朱者赤的博客 精选专栏&#xff1a;&#x1f517;经典算法 作者简介&#xff1a;阿里非典型程序员一枚 &#xff0c;记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; ❤️觉得文章还…

004 秒杀下单

文章目录 超卖问题方案一方案二方案三aop锁(单机锁)aop锁(单机锁)pom.xmlLockAspect.javaServiceLock.java 分布式锁Mysql分布式锁Redis分布式锁ServiceRedisLock.javaLockRedisAspect.java 下单性能优化数据一致性解决一致性问题异步同步库存 秒杀下单业务步骤: 1.数据校验(身…

LeetCode 543.二叉树的直径

题目描述 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5]…

WebStorm2024版 将项目上传到gitee

目录 一、准备 WebStorm gitee 二、上传代码到Gitee 三、过程中遇到的问题 报错&#xff1a;You may want to first integrate the remote changes (e.g., git pull ...) before pushing again. 报错&#xff1a;fatal: refusing to merge unrelated histories 报错&a…