Leetcode 1206. 设计跳表

news/2025/2/25 1:20:53/文章来源:https://www.cnblogs.com/smartljy/p/18735263

Leetcode 1206. 设计跳表

这道题的经验包:

  1. 跳表的知识点

  2. 现代C++的随机数操作

随机数知识点

以下是现代 C++(C++11 及以后版本)中随机数生成器的使用指南,包含代码示例和关键概念说明:


一、现代 C++ 随机数库的组成

现代 C++ 通过 <random> 头文件提供随机数库,包含三个核心组件:

  1. 随机数引擎(生成原始随机数)
  2. 分布器(将原始数映射到特定范围/分布)
  3. 适配器(修改引擎行为)

二、基本使用步骤(以骰子为例)

#include <iostream>
#include <random>int main() {// 1. 创建随机数引擎(使用硬件熵初始化)std::random_device rd;  std::mt19937 gen(rd());  // 使用 Mersenne Twister 算法引擎// 2. 定义分布器(1~6 的均匀整数分布)std::uniform_int_distribution<int> dist(1, 6);// 3. 生成随机数for (int i = 0; i < 5; ++i) {std::cout << dist(gen) << " ";  // 输出类似:3 5 2 6 4}
}

三、核心组件详解

1. 随机数引擎(Engines)

引擎类型 特性 典型用途
std::mt19937 梅森旋转算法,高质量随机数 通用场景
std::mt19937_64 64 位版本 需要更大范围
std::minstd_rand 线性同余算法,速度更快 性能敏感场景
std::random_device 可能使用硬件熵源(非确定性) 种子生成

2. 分布器(Distributions)

分布类型 描述 示例代码
uniform_int_distribution 均匀整数分布 dist(1, 100) → 1~100 整数
uniform_real_distribution 均匀浮点数分布 dist(0.0, 1.0) → [0.0, 1.0)
normal_distribution 正态(高斯)分布 dist(均值, 标准差)
bernoulli_distribution 布尔分布(真/假) dist(0.7) → 70% 概率为 true
discrete_distribution 自定义离散概率分布 dist{1,2,3} → 概率权重 1:2:3

四、最佳实践

1. 种子初始化

// 推荐方式:混合种子(更安全)
std::random_device rd;
std::seed_seq seeds{rd(), static_cast<uint64_t>(std::time(nullptr))};
std::mt19937_64 gen(seeds);

2. 线程安全

// 每个线程创建独立引擎实例(避免竞争)
void thread_task() {thread_local std::mt19937 gen(std::random_device{}());std::uniform_int_distribution<int> dist(1, 6);// 使用 gen 和 dist...
}

3. 性能优化

// 重用引擎和分布器(避免重复构造)
std::mt19937& get_engine() {static std::mt19937 gen(std::random_device{}());return gen;
}int get_random() {static std::uniform_int_distribution<int> dist(1, 100);return dist(get_engine());
}

五、完整代码示例(正态分布)

#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <iomanip>int main() {// 1. 初始化引擎std::random_device rd;std::mt19937 gen(rd());// 2. 定义正态分布(均值=0,标准差=1)std::normal_distribution<double> dist(0.0, 1.0);// 3. 生成 1000 个样本std::vector<double> data;std::generate_n(std::back_inserter(data), 1000, [&] { return dist(gen); });// 4. 统计分布情况auto [min, max] = std::minmax_element(data.begin(), data.end());std::cout << "范围: [" << *min << ", " << *max << "]\n";// 5. 输出直方图std::map<int, int> hist;for (double x : data) {++hist[std::round(x)];}for (auto [value, count] : hist) {std::cout << std::setw(2) << value << " " << std::string(count/5, '*') << "\n";}
}

六、常见错误

  1. 重复创建引擎

    // 错误:每次调用都新建引擎(导致相同序列)
    int bad_random() {std::mt19937 gen(std::random_device{}());std::uniform_int_distribution<int> dist(1, 100);return dist(gen);
    }
    
  2. 误用 std::random_device

    // 某些平台可能回退到伪随机(需检查熵)
    if (std::random_device{}.entropy() == 0) {std::cerr << "Warning: 当前平台未提供真随机源\n";
    }
    

总结

现代 C++ 的 <random> 库提供了:

  • ✅ 更高质量的随机数生成
  • ✅ 灵活的分布控制
  • ✅ 更好的线程安全性
  • ✅ 可预测的随机序列(通过固定种子)

建议优先使用此库替代传统的 rand()srand()

正解代码(附详细注释)

constexpr double P = 0.5;
constexpr int MAX_LEVEL = 16;
struct SkipNode {int val;vector<SkipNode*> list;SkipNode(int _val, int _max_level = MAX_LEVEL): val(_val), list(_max_level, nullptr) {}
};class Skiplist {public:Skiplist() : head(new SkipNode(-1)), level(0) {}bool search(int target) {SkipNode *cur = this->head;// 从上往下找for(int i = level - 1; i >= 0; i--) {while(cur->list[i] && cur->list[i]->val < target)cur = cur->list[i];}cur = cur->list[0];// 要小心访问到空地址return cur && cur->val == target;}void add(int num) {// 这里要开MAX_LEVEL个,因为后面的新level可能会超过现有的levelstd::vector<SkipNode*> update(MAX_LEVEL, head);SkipNode *cur = this->head;for(int i = level - 1; i >= 0; i--) {while(cur->list[i] && cur->list[i]->val < num)cur = cur->list[i];// 用update记录每层最后一个被访问的节点update[i] = cur;}SkipNode *_insert = new SkipNode(num);int lv = gen_lv();// 更新层数level = std::max(level, lv);// 新节点插入在update[i]的后面for(int i = 0; i < lv; i++) {_insert->list[i] = update[i]->list[i];update[i]->list[i] = _insert;}}bool erase(int num) {// 这里只要开level个,因为在这个函数level只会变小不会变大std::vector<SkipNode*> update(level, head);// 跟erase一模一样SkipNode *cur = head;for(int i = level - 1; i >= 0; i--) {while(cur->list[i] && cur->list[i]->val < num)cur = cur->list[i];// 用update记录每层最后一个被访问的节点update[i] = cur;}// 我们没必要记录cur的prev,因为update已经发挥了这个作用cur = cur->list[0];if (!cur || cur->val != num)return false;// 要先完成连接的修改再delete掉cur,不然会空悬指针for(int i = 0; i < level; i++) {// 如果cur在这一层没有出现,那么再往上它都不会出现了if (update[i]->list[i] != cur)break;update[i]->list[i] = cur->list[i];}// 可以安全地删除cur了delete cur;// 更新层数while(level > 1 && head->list[level - 1] == nullptr)level--;return true;}private:SkipNode* head;int level;// 注意,生成引擎不是用device对象来初始化的,使用device对象的调用结果来初始化的std::mt19937 gen{std::random_device{}()};std::uniform_real_distribution<double> dis{0.0, 1.0};int gen_lv() {// level初始化为0是因为一开始整个跳表是空的,所以我们的层数相当于0,但是add操作是一定会加入实际节点的,那么它的层数就应该至少是1int res = 1;// 分布器传入的是引擎对象while(dis(gen) <= P && res < MAX_LEVEL) res++;return res;}
};

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

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

相关文章

日志(logging)

【一】函数式简单配置 import logging logging.debug(debug message) logging.info(info message) logging.warning(warning message) logging.error(error message) logging.critical(critical message)默认情况下Python的logging模块将日志打印到了标准输出中,且只…

解决Docker连接XQuartz报错

背景 打开XQuartz,在桌面右上角菜单栏中的XQuartz偏好设置中的安全性中启用 "允许从网络客户端连接",并重启XQuartz生效,后续操作中要保持XQuartz为打开状态,不要退出XQuartz报错内容: root@9b90039a4d17:~# ros2 run turtlesim turtlesim_node Authorization r…

MySQL的四种事务隔离级别

一.MySQL四大隔离级别 事务隔离级别 1.读未提交 (Read Uncommitted) 允许一个事务读取另一个事务未提交的数据。 2.读已提交 (Read Committed) 一个事务只能读取另一个事务已经提交的数据。 3.可重复读 (Repeatable Read) 保证在一个事务内多次读取同一数据时,其结果是一致的。…

持续分享干货!清华出品《DeepSeek如何赋能职场》PDF可下载

🔥今天分享的是清华出品《DeepSeek如何赋能职场》的PPT,内容涵盖了DeepSeek运行模式的讲解,如何向DeepSeek提问,如何构建提示词,结合实际多个实际应用场景,详细的介绍了DeepSeek结合其他多模态AI模型持续赋能职场的方法。提示词框架如何使用DeepSeek制作可视化图表如何使…

「跟着渡一学前端」并发请求实现

学习资源 并发请求 【渡一教育】 完整代码 function concurRequest(urls, maxNum) {if (urls.length === 0) {return Promise.resolve([]);}return new Promise((resolve) => {let nextIndex = 0;let finishCount = 0;const result = [];async function _request() {if (nex…

软工作业1

作业相关信息这个作业属于哪个课程 软件工程 这个作业要求在哪里 自我介绍+软工5问 这个作业的目标 自我介绍,了解软件工程基本概念个人介绍 Im YiLaiL YiLaiL/YiLaiL is a ✨ special ✨ repository because its README.md (this file) appears on your GitHub profile.🔭 …

EmEdit设置缓存目录临时文件夹

前言全局说明一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737 EmEditor Professional (64-bit) Version 17.2.4二、打开大文本控制器三、点击右边大文本控制器上 自定义四、选择比较大的磁盘空间作为缓存空间免责声明:本号所涉及内容仅供安全研究与教学使用,如出现其他…

谈谈 ES 6.8 到 7.10 的功能变迁(3)- 查询方法篇

上一篇咱们了解了 ES 7.10 相较于 ES 6.8 新增的字段类型,这一篇我们继续了解新增的查询方法。 Interval 间隔查询: 功能介绍 Interval 查询,词项间距查询,可以根据匹配词项的顺序、间距和接近度对文档进行排名。主要解决的查询场景“创建一个多搜索词匹配的查询,同时保留…

【蓝牙小程序】在微信小程序中使用 ECharts

echarts-for-weixin 项目提供了一个小程序组件,用这种方式可以方便地使用 ECharts。 使用方式下载该项目 如有必要,将 ec-canvas 目录下的 echarts.js 替换为最新版的 ECharts。如果希望减小包体积大小,可以使用自定义构建生成并替换 echarts.js pages 目录下是使用的示例文…

Virtual Box设置双网卡

一、硬件 1.为虚拟机添加网卡2.配置网卡二、软件 3.获取mac地址 命令:ip link [root@vbox network-scripts]# ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000link/loopback 00:00:00:00:00:00 brd…

简单逆向Java程序

前置 来源 这个程序是我同学编写的一个学生分数管理系统,我将对这个已经编译的程序进行测试、逆向,找出其中的问题,并进行改进。 运行环境macOS 15.4 IntelliJ IDEA 2024.2.3 OpenJDK 23.0.2 TomCat 11.0.4 Safari 15.4运行结果主要问题 在使用了这个程序之后,我发现了以下…

信息论概述

1 信息与消息 1.1 信息 1.1.1 信息的定义 信息是信息论中最基本、最重要的概念 香农信息的定义:信息是事物运动状态或存在方式的不确定描述 1.1.2 (香农)信息的度量样本空间:对于我们需要描述的事物中,事物可能存在不同的状态,即事物展现出来的多种状态。那么为了便于形容事…