当前位置: 首页 > news >正文

LeetCode 1206. 实现跳表

不使用任何库函数,设计一个跳表。

跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。

例如,一个跳表包含 [30, 40, 50, 60, 70, 90],然后增加 80、45 到跳表中,以下图的方式操作:

 

跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)。

注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。

题目分析

跳表其实就是加了几层索引的链表,一共有 N 层,以 0 ~ N-1 层表示,设第 0 层是原链表,抽取其中部分元素,在第 1 层形成新的链表,上下层的相同元素之间连起来;再抽取第 1 层部分元素,构成第 2 层,以此类推。

为什么需要random函数呢?

需要某种手段来维护索引与原始链表大小之间的平衡,也就是说,如果链表中结点多了,索引结点就相应地增加一些,避免复杂度退化,以及查找、插入、删除操作性能下降。

如果你了解红黑树、AVL 树这样平衡二叉树,你就知道它们是通过左右旋的方式保持左右子树的大小平衡,而跳表是通过随机函数来维护前面提到的“平衡性”。

【查找】

跳表其实就是加了几层索引的链表,一共有 N 层,以 0 ~ N-1 层表示,设第 0 层是原链表,抽取其中部分元素,在第 1 层形成新的链表,上下层的相同元素之间连起来;再抽取第 1 层部分元素,构成第 2 层,以此类推。
具体选哪些元素呢?题目给的是coinFlip,也就是每次插入元素的时候,都进行一次 50% 概率的判断,如果 true 则向上层添加一个索引,如果 false 就不加了。

【添加和删除】

添加和删除的时候,存在数据重复的问题,经过我的测试,发现题目要求的是,将重复的数据当做不同的值来对待,即存了一个元素 9 ,再存一个 9 ,此时表里有俩 9 ,相互独立,删除一个 9 ,表里应该还剩余有一个9。

由于添加和删除元素都是从底层开始,而在查找的时候是从顶层开始查找的,因此可以使用一个数组记录每层的“跳跃节点”的位置,就不必反复的从顶层开始查找每层的位置了。

在添加的时候,首先是查找到添加位置,过程与查找类似,首先找到表中 >= 插入元素 的最小节点位置,然后插入节点, 50% 概率判别,如果需要添加索引,就添加索引,继续 50% 概率判别,直到结束。

在删除的时候,首先也是查找,找到表中所有 = 插入元素的节点位置(每层只找一个,找到直接跳层即可),然后挨个删除。

代码实现

class Skiplist {class SkipListNode {int val;int cnt;  // 当前val出现的次数SkipListNode[] levels;  // start from 0SkipListNode() {levels = new SkipListNode[MAX_LEVEL];}}private double p = 0.5;private int MAX_LEVEL = 16;private SkipListNode head;  // 头结点private int level;  // private Random random;public Skiplist() {// 保存此level有利于查询(以及其他操作)level = 0;  // 当前 skiplist的高度(所有数字 level数最大的)head = new SkipListNode();random = new Random();}// 返回target是否存在于跳表中public boolean search(int target) {SkipListNode curNode = head;for (int i = level-1; i >= 0; i--) {while (curNode.levels[i] != null && curNode.levels[i].val < target) {curNode = curNode.levels[i];}}curNode = curNode.levels[0];return (curNode != null && curNode.val == target);}// 插入一个元素到跳表。public void add(int num) {SkipListNode curNode = head;// 记录每层能访问的最右节点SkipListNode[] levelTails = new SkipListNode[MAX_LEVEL];for (int i = level-1; i >= 0; i--) {while (curNode.levels[i] != null && curNode.levels[i].val < num) {curNode = curNode.levels[i];}levelTails[i] = curNode;}curNode = curNode.levels[0];if (curNode != null && curNode.val == num) {// 已存在,cnt 加1curNode.cnt++;} else {// 插入int newLevel = randomLevel();if (newLevel > level) {for (int i = level; i < newLevel; i++) {levelTails[i] = head;}level = newLevel;}SkipListNode newNode = new SkipListNode();newNode.val = num;newNode.cnt = 1;for (int i = 0; i < level; i++) {newNode.levels[i] = levelTails[i].levels[i];levelTails[i].levels[i] = newNode;}}}private int randomLevel() {int level = 1;  // 注意思考此处为什么是 1 ?while (random.nextDouble() < p && level < MAX_LEVEL) {level++;}return level > MAX_LEVEL ? MAX_LEVEL : level;}// 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。public boolean erase(int num) {SkipListNode curNode = head;// 记录每层能访问的最右节点SkipListNode[] levelTails = new SkipListNode[MAX_LEVEL];for (int i = level-1; i >= 0; i--) {while (curNode.levels[i] != null && curNode.levels[i].val < num) {curNode = curNode.levels[i];}levelTails[i] = curNode;}curNode = curNode.levels[0];if (curNode != null && curNode.val == num) {if (curNode.cnt > 1) {curNode.cnt--;return true;}// 存在,删除for (int i = 0; i < level; i++) {if (levelTails[i].levels[i] != curNode) {break;}levelTails[i].levels[i] = curNode.levels[i];}while (level > 0 && head.levels[level-1] == null) {level--;}return true;} return false;}
}

代码实现二

class Skiplist {int MAX_LEVEL = 16;int curLevel;Node head;public Skiplist() {curLevel = 1;head = new Node(-1);head.next_point = new Node[MAX_LEVEL];}public boolean search(int target) {Node temp = head;//从最顶层索引找for (int i = MAX_LEVEL - 1; i >=0; i--) {while (temp.next_point[i] != null && temp.next_point[i].val <= target) {if (temp.next_point[i].val == target)return true;elsetemp = temp.next_point[i];}}// 判断temp的下个节点是否满足条件if (temp.next_point[0] != null && temp.next_point[0].val == target)return true;return false;}public void add(int num) {int level = randomLevel(0.5);if (level > curLevel)curLevel = level;Node node = new Node(num);node.next_point = new Node[level];Node[] forward = new Node[level];Arrays.fill(forward, head);Node temp = head;// 找到前驱节点for (int i = level - 1; i >= 0; i--) {while (temp.next_point[i] != null && temp.next_point[i].val < num)temp = temp.next_point[i];forward[i] = temp;}// 更新连接for (int i = 0; i < level; i++) {node.next_point[i] = forward[i].next_point[i];forward[i].next_point[i] = node;}}public boolean erase(int num) {Node[] forward = new Node[curLevel];Node temp = head;for (int i = curLevel - 1; i >= 0; i--) {while (temp.next_point[i] != null && temp.next_point[i].val < num)temp = temp.next_point[i];forward[i] = temp;}boolean res = false;if (temp.next_point[0] != null && temp.next_point[0].val == num) {res = true;// 更新连接for (int i = curLevel - 1; i >= 0; i--)if (forward[i].next_point[i] != null && forward[i].next_point[i].val == num)forward[i].next_point[i] = forward[i].next_point[i].next_point[i];}// 删除孤立节点层while (curLevel > 1 && head.next_point[curLevel - 1] == null)curLevel -= 1;return res;}public int randomLevel(double p) {int level = 1;while (Math.random() < p && level < MAX_LEVEL)level++;return level;}
}class Node {int val;Node[] next_point;public Node(int val) {this.val = val;}
}

相关文章:

LeetCode 1206. 实现跳表

不使用任何库函数&#xff0c;设计一个跳表。 跳表是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树&#xff0c;其功能与性能相当&#xff0c;并且跳表的代码长度相较下更短&#xff0c;其设计思想与链表相似。 例如&#xff0c;一个跳表包…...

离散数学_九章:关系(2)

9.2 n元关系及其应用 1、n元关系&#xff0c;关系的域&#xff0c;关系的阶2、数据库和关系 1. 数据库 2. 主键 3. 复合主键 3、n元关系的运算 1. 选择运算 (Select) 2. 投影运算 (Project) 3. 连接运算 (Join) n元关系&#xff1a;两个以上集合的元素间的关系 1、n元关系…...

[ubuntu][原创]通过apt方式去安装libnccl库

ubuntu18.04版本安装流程&#xff1a; wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600 sudo apt-key adv --fetch-keys https://develo…...

YonLinker连接集成平台构建新一代产业互联根基

近日&#xff0c;由用友公司主办的“2023用友BIP技术大会“在用友产业园&#xff08;北京&#xff09;盛大召开&#xff0c;用友介绍了更懂企业业务的用友BIP-iuap平台&#xff0c;并发布了全面数智化能力体系&#xff0c;助力企业升级数智化底座&#xff0c;加强加速数智化推进…...

泛型的详解

泛型的理解和好处 首先我们先来看看泛型的好处 1)编译时&#xff0c;检查添加元素的类型&#xff0c;提高了安全性 2)减少了类型转换的次数&#xff0c;提高效率[说明] 不使用泛型 Dog -> Object -> Dog//放入到ArrayList 会先转成Object&#xff0c;在取出时&#x…...

用科技创造未来!流辰信息技术助您实现高效办公

随着社会的迅猛发展&#xff0c;科技的力量无处不见。它正在悄悄地改变整个社会&#xff0c;让人类变得进步和文明&#xff0c;让生活变得便捷和高效。在办公自动化强劲发展的今天&#xff0c;流辰信息技术让通信业、电网、汽车、物流等领域的企业实现了高效办公&#xff0c;数…...

基于R语言APSIM模型

随着数字农业和智慧农业的发展&#xff0c;基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。 APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物…...

块状链表实现BigString大字符串操作(golang)

前言 块状链表是介于链表和数组之间的数据结构&#xff0c;能够在 O ( n ) O(\sqrt{n}) O(n ​)时间内完成插入、删除、访问操作。 数据结构如图所示。假设最大容量为 n n n, 则它有一个长度为 s n s\sqrt{n} sn ​的链表。链表中每个结点是一个长度为 2 n 2 \times \sqrt{…...

项目问题记录(持续更新)

1.在 yarn install的时候报 error achrinza/node-ipc9.2.2: The engine "node" is incompatible with this module. Expected version "8 || 10 || 12 || 14 || 16 || 17". Got "20.1.0" error Found incompatible module.需要执行 yarn config…...

Linux的进程

目录 一、进程占用的内存资源 二、进程的系统环境 三、进程一直在切换 四、父进程和子进程 五、进程状态 六、查看进程 1.ps -ef 列出所有进程 2.ps -lax 列出所有进程 3.ps aux列出所有进程 4.树形列出所有进程 七、作业&#xff08;用来查看管理进程&#xff09; …...

与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!!

与其焦虑被 AI 取代或猜测前端是否已死, 不如看看 vertical-align 扎实你的基础!!! vertical-align 设置 display 值为 inline, inline-block 和 table-cell 的元素竖直对齐方式. 从 line-height: normal 究竟是多高说起 我们先来看一段代码, 分析一下为什么第二行的行高, 也就…...

路由、交换机、集线器、DNS服务器、广域网/局域网、端口、MTU

前言&#xff1a;网络名词术语解析(自行阅读扫盲)&#xff0c;推荐大家去读户根勤的《网络是怎样连接的》 路由(route)&#xff1a; 数据包从源地址到目的地址所经过的路径&#xff0c;由一系列路由节点组成。某个路由节点为数据包选择投递方向的选路过程。 路由器工作原理 路…...

在全志V851S开发板上进行屏幕触摸适配

1.修改屏幕驱动 从ft6236 &#xff08;删掉&#xff0c;不要保留&#xff09;&#xff0c;改为下面的 路径&#xff1a;/home/wells/tina-v853-open/tina-v853-open/device/config/chips/v851s/configs/lizard/board.dts&#xff08;注意路径&#xff0c;要设置为自己的实际路…...

字符串拷贝时的内存重叠问题

字符串拷贝时的内存重叠问题 1.什么是内存重叠 拷贝的目的地址在源地址的范围内&#xff0c;有重叠。 如在写程序的过程中&#xff0c;我们用到的strcpy这个拷贝函数&#xff0c;在这个函数中我们定义一个目的地址&#xff0c;一个源地址&#xff0c;在拷贝的过程中如果内存重…...

告别PPT手残党!这6款AI神器,让你秒变PPT王者!

如果你是一个PPT手残党&#xff0c;每每制作PPT总是让你焦头烂额&#xff0c;那么你一定需要这篇幽默拉风的推广文案&#xff01; 我向你保证&#xff0c;这篇文案将帮助你发现6款AI自动生成PPT的神器&#xff0c;让你告别PPT手残党的身份&#xff0c;成为一名PPT王者。 无论…...

JVM配置与优化

参考&#xff1a; JVM内存分区及作用&#xff08;JDK8&#xff09; https://blog.csdn.net/BigBug_500/article/details/104734957 java 进程占用系统内存过高分析 https://blog.csdn.net/fxh13579/article/details/104754340 Java之jvm和线程的内存 https://blog.csdn.ne…...

电力系统储能调峰、调频模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

C++基础之类、对象一(类的定义,作用域、this指针)

目录 面向对象的编程 类的引入 简介 类的定义 简介 访问限定符 命名规则 封装 简介 类的作用域 类的大小及存储模型 this指针 简介 面向对象的编程 C与C语言不同&#xff0c;C是面向对象的编程&#xff0c;那么什么是面向对象的编程呢&#xff1f; C语言编程&#xff0c;规定…...

javaScript---设计模式-封装与对象

目录 1、封装对象时的设计模式 2、基本结构与应用示例 2.1 工厂模式 2.2 建造者模式 2.3 单例模式 封装的目的&#xff1a;①定义变量不会污染外部&#xff1b;②能作为一个模块调用&#xff1b;③遵循开闭原则。 好的封装&#xff08;不可见、留接口&#xff09;&#xff1a;①…...

【消息中间件】kafka高性能设计之内存池

文章目录 前言实现创建内存池分配内存释放内存 总结 前言 Kafka的内存池是一个用于管理内存分配的缓存区域。它通过在内存上保留一块固定大小的内存池&#xff0c;用于分配消息缓存、批处理缓存等对象&#xff0c;以减少频繁调用内存分配函数的开销。 Kafka内存池的实现利用了…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋

随着工业以太网的发展&#xff0c;其高效、便捷、协议开放、易于冗余等诸多优点&#xff0c;被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口&#xff0c;具有实时性、开放性&#xff0c;使用TCP/IP和IT标准&#xff0c;符合基于工业以太网的…...