最小缺失正整数:我的暂存标记法,怎样做到O(n)时间和O(1)空间
题目给一个整数数组要找没有出现过的最小正整数。比如[-5,3,2,3]1没出现答案就是1[1,2,3]里面前三个正整数都有了答案是4。负数不参与这次查找重复出现也只算出现过。我先想到做一张出现表读到正整数x就标记x出现过。最后从1开始看哪个没标记就返回哪个。我还想过用一个min记着当前最小缺失值。比如[-5,3,2,3,1]先遇到3和2min仍是1直到最后遇到1才连续跳过已经出现的1、2、3到4。这样确实能做。但如果数组里有一个特别大的正数表也要开到那么大吗不用。数组只有n个位置。如果1到n都出现了正好已经占满这些位置最小缺失值就是n1如果没有全出现缺的那个就在1到n里。所以只用记录1到n。0、负数和大于n的数可以忽略。这个办法时间O(n)另开出现表要O(n)空间。min有时一口气走很多步但它只向前、不回退累计也就是线性移动。原题要求时间尽可能高效并没有额外规定必须常数空间。原数组读过的位置能不能拿来标记我不太想再开一个数组。原数组本身有n个格子而我们恰好只需要记录n个正整数能不能直接借这些格子约定数字1对应下标0数字2对应下标1数字x对应下标x-1。往对应格子写一个标记就表示这个数出现过。这里不是说第一个格子原来必须存1。我们只是重新分配它的用途以后用第一格回答“1见过吗”用第二格回答“2见过吗”。原数组可能乱序格子里的旧数与这个新用途没有关系。先拿[3,1,2]试试。读到3要去下标2写标记。可那个位置原来是2这个2也得处理直接覆盖它就丢了。如果这时不管2直接写标记我们知道3出现过了却再也不能从那格读到原来的2。这就是还需要解决的问题借用格子不等于可以丢掉它原来存的数。所以我想到写之前先把2拿出来暂存在一个变量里。写完3的标记再处理手里的2。后面还挤出别的数就继续拿住、继续处理。我们先用-1表示“出现过”把这个过程走完输入本来就有负数怎么办后面再补。手里这个数一次次变成了什么先从下标0取出3放到变量x里。原来的格子改成0表示这个值已经取走了。现在要记录3去的是第三格也就是下标2。那里原来存着2。先别覆盖用displaced把这个旧值复制出来这时数组还没改手里的x也仍是3。然后才把第三格写成-1表示3已经出现。数组变成[0,1,-1]。当前的3记录完了下一步处理谁处理刚保住的2所以把displaced交给x令x变成2。接着记录2。它对应下标1那里原来是1。先拿住1再写-1。数组变成[0,-1,-1]手里变成1。最后记录1。它对应下标0那里已经是0。写成-1拿到的旧值是0不用再追下去了。做完哪一步数组手里的x从下标0取出3[0,1,2]3标记3拿走旧值2[0,1,-1]2标记2拿走旧值1[0,-1,-1]1标记1拿到0[-1,-1,-1]0停止这时数组不再保存原来的排列而是保存出现情况三个格子都是-1说明1、2、3都出现过答案是4。那后面还有一个7谁来处理我当时对只举三元素的例子还不放心如果后面还有别的数呢所以这里需要分清两个循环。内层while只追当前手里这串被挤出的数外层for还要继续检查数组后面的格子。比如换成[3,1,2,7,1,2,3]长度是7。前面那串处理完只标记了1、2、3后面的7还在那里前三个处理后[-1,-1,-1,7,1,2,3] 外层走到7 [-1,-1,-1,0,1,2,-1]手里拿到旧值33已经标记过这串就停。但外层还会继续走到后面的1和2把它们取走发现各自已经记录过也停止。最终是[-1,-1,-1,0,0,0,-1]。下标3没有标记表示4没出现返回4。已经是-1的格子要直接跳过。它存的是我们留下的出现信息不能再清成0否则刚记好的信息又被擦掉了。原来就有-1还有重复值怎么办刚才先用了-1当标记但题目允许原数组出现-1。如果输入是[-1,2]第一个格子本来就是-1并不表示1出现过。解决这件事不需要找一个更稀奇的标记。我们前面已经知道只有1到n需要保留。先把范围外的原值全部清成0原始-1也就没了。以后出现的-1只可能是程序写入的。[-1,2]先变成[0,2]再记录2结果是[0,-1]。第一个格子仍没有标记所以答案正确地是1。重复值也不用反复记录。比如拿到第二个2发现下标1已经是-1说明2的出现信息早就留下了直接停。手里的重复2不再有新的作用。对着上面的过程看代码displaced就是每次先拿走的旧值int firstMissingMark(int A[], int n) { // 消除原始 -1 与标记 -1 的歧义范围外数据也无须考虑 for (int i 0; i n; i) { if (A[i] 0 || A[i] n) A[i] 0; } for (int i 0; i n; i) { if (A[i] -1) continue; // 此位置已被标记 int x A[i]; A[i] 0; // 取走当前值释放当前位置 while (x 1 x n A[x - 1] ! -1) { int displaced A[x - 1]; A[x - 1] -1; // 记录 x 已出现 x displaced; // 处理被挤出来的旧值 } } for (int i 0; i n; i) if (A[i] ! -1) return i 1; return n 1; }代码先检查x是否在1到n再访问A[x-1]。手里拿到0时条件就不成立不会去访问下标-1。while会不会把时间变成平方看内层每次真正做了什么找到一个还没标记的格子把它变成-1。这个标记以后不会取消目标格已经是-1时也不会继续处理。整个数组只有n格因此所有内层循环加起来成功标记最多n次并不是每个外层位置都能再跑n次。加上预处理、外层扫描和最后查缺总时间仍是O(n)。手里只暂存固定数量的值没有再开数组额外空间O(1)。交换法是另一种安置旧值的办法前面我把旧值拿在手里。还有一种常见做法是让数字x到A[x-1]同时把那里的旧值换回当前格子继续处理。同样从[3,1,2]开始3和下标2的2交换得到[2,1,3]再把当前的2和下标1的1交换得到[1,2,3]。它用“对应格子里就是这个数”表示出现过而不是写-1int firstMissingSwap(int A[], int n) { for (int i 0; i n; i) { while (A[i] 1 A[i] n A[i] ! A[A[i] - 1]) { int x A[i]; int temp A[x - 1]; A[x - 1] x; A[i] temp; } } for (int i 0; i n; i) if (A[i] ! i 1) return i 1; return n 1; }这里要留意重复值。[2,2,1]中第一个2想去的位置已经是2再交换数组不会变化继续while就停不下来。所以先判断目标位置是否已经是同一个数。每次有效交换都会让一个尚未归位的位置存好对应的数已归位的位置不会被有效交换破坏。总有效交换次数也是O(n)。两种办法都修改输入。区别在于被挤出的值放哪里一个交给临时变量一个换回当前格子。完整验证会把它们与另开出现表的参照分别比较。这两份C实现约定0≤nINT_MAXn大于0时数组有效空数组返回1。若为了保留输入而复制整个数组复制占用的空间要另外算。题意按2018年408原卷第41题整理原卷来自厦门工学院公开备考资料。下面保留我的解题复盘C实现与验证是补充不是官方答案。附录完整验证程序参照在未修改的输入上从1逐个找缺失值两种原地算法分别复制输入后运行。[-1,2]、[2,2,1]等情形都在原有穷举域内。完整程序包含上面的题解函数和独立参照。保留源码是为了读者能自己改输入、定位错误不需要先读完它才能理解正文。gcc -stdc11 -Wall -Wextra -Werror -pedantic -fsanitizeaddress,undefined -fno-omit-frame-pointer lab.c -o lab ./lab#include assert.h #include limits.h #include stdbool.h #include stddef.h #include stdio.h #include stdlib.h int firstMissingSwap(int A[], int n) { for (int i 0; i n; i) { while (A[i] 1 A[i] n A[i] ! A[A[i] - 1]) { int x A[i]; int temp A[x - 1]; A[x - 1] x; A[i] temp; } } for (int i 0; i n; i) if (A[i] ! i 1) return i 1; return n 1; } int firstMissingMark(int A[], int n) { // 消除原始 -1 与标记 -1 的歧义范围外数据也无须考虑 for (int i 0; i n; i) { if (A[i] 0 || A[i] n) A[i] 0; } for (int i 0; i n; i) { if (A[i] -1) continue; // 此位置已被标记 int x A[i]; A[i] 0; // 取走当前值释放当前位置 while (x 1 x n A[x - 1] ! -1) { int displaced A[x - 1]; A[x - 1] -1; // 记录 x 已出现 x displaced; // 处理被挤出来的旧值 } } for (int i 0; i n; i) if (A[i] ! -1) return i 1; return n 1; } int missingOracle(const int a[], int n) { for (int candidate 1; candidate n 1; candidate) { int found 0; for (int i 0; i n; i) if (a[i] candidate) found 1; if (!found) return candidate; } abort(); } unsigned rng_state 4082018; unsigned rnd(void) { rng_state rng_state * 1664525u 1013904223u; return rng_state; } void verify(const int a[], int n) { int b[96], c[96]; for (int i 0; i n; i) b[i] c[i] a[i]; int expected missingOracle(a, n); assert(firstMissingSwap(b, n) expected); assert(firstMissingMark(c, n) expected); } int main(void) { size_t cases 0; int duplicate[3] {2, 2, 1}; verify(duplicate, 3); const int alphabet[8] {INT_MIN, -1, 0, 1, 2, 3, 6, INT_MAX}; for (int n 0; n 6; n) { size_t total 1; for (int i 0; i n; i) total * 8; for (size_t mask 0; mask total; mask) { int a[96]; size_t x mask; for (int i 0; i n; i) { a[i] alphabet[x % 8]; x / 8; } verify(a, n); cases; } } for (int trial 0; trial 10000; trial) { int a[96], n (int)(rnd() % 97); for (int i 0; i n; i) a[i] (int)(rnd() % 150) - 25; verify(a, n); cases; } printf(PASS missing cases%zu\n, cases); return 0; }这次改写后重新以严格编译选项和AddressSanitizer、UndefinedBehaviorSanitizer运行了上面的完整C程序309593组数组与边界检查通过输出如下。题解代码与此前版本逐字相同。assert不能通过-DNDEBUG关闭。PASS missing cases309593有限测试是发现错误的证据不代替正文的正确性与复杂度理由。

相关新闻

ONLYOFFICE AI本地部署实战:从Docker安装到AI接口接入全攻略

ONLYOFFICE AI本地部署实战:从Docker安装到AI接口接入全攻略

说实话,这个标题写出来的时候,我自己都有点意外。用了快十年的老牌办公套件,说换就换了,而且换完之后不仅没后悔,还顺手把团队里另外两个人也拉了过来。这半个月里我干了不少蠢事,踩了不少坑,但…

2026/10/18 5:58:46 阅读更多 →
微信小程序进入广告全链路实战:预加载、时机控制与合规降级

微信小程序进入广告全链路实战:预加载、时机控制与合规降级

简介:本资源是一份面向微信小程序初学者与进阶开发者的实用代码实践指南,聚焦用户首次进入小程序时展示全屏广告的核心需求,解决品牌曝光与流量变现场景下的广告接入问题。资源以PDF文档形式提供,共1个文件,大小仅30KB…

2026/10/18 5:57:46 阅读更多 →
整车CFD仿真中旋转壁面法与MRF方法对比及选型指南

整车CFD仿真中旋转壁面法与MRF方法对比及选型指南

简介:这份PDF面向整车空气动力学仿真工程师与CFD学习者,聚焦车轮旋转处理这一风阻优化关键环节,系统对比旋转壁面法与MRF法在整车高速工况下的仿真差异。资源包仅含1个PDF文件,大小约768KB,内容以图文并茂的技术论文形…

2026/10/18 5:57:46 阅读更多 →

最新新闻

MATLAB实现物元分析法:工程级可拓评价系统

MATLAB实现物元分析法:工程级可拓评价系统

简介:本资源是一套基于MATLAB实现的物元分析法完整计算工具包,面向高校科研人员、系统工程与管理科学方向的研究生及可拓学初学者,用于解决多指标综合评价中的定量化建模难题。资源包含5个核心文件:4个Excel数据表(分别…

2026/10/18 6:59:58 阅读更多 →
Python + Vue校园社交平台开发实战:Flask/Django选型与前后端分离架构详解

Python + Vue校园社交平台开发实战:Flask/Django选型与前后端分离架构详解

校园社交平台这个方向,我在实际带项目的过程中见过太多次了。每次有同学拿着类似的题目来找我,我都先问一句:你确定要做社交?不是做一个课程设计交差就行?这个问题的答案,直接决定了项目后面是走Flask轻量路…

2026/10/18 6:59:58 阅读更多 →
技术标题解析方法论:如何科学验证模糊缩写rea

技术标题解析方法论:如何科学验证模糊缩写rea

项目标题为“rea”,但提供的输入内容中未给出任何有效上下文:无项目正文、无关键词列表、无摘要描述,相关热搜词与网络热词栏为空,搜索内容部分亦为空白。这意味着——该标题当前不具备可拆解的信息基础。作为一名从业十余年、经手…

2026/10/18 6:59:58 阅读更多 →
银行股实操指南:底仓+股息+波段,建立稳健投资框架

银行股实操指南:底仓+股息+波段,建立稳健投资框架

1. 项目概述“股市赚钱学概论:答疑:如何实操大银行”这个标题,乍看像是一个玩笑——把“赚钱学”和“大银行”放在一起,既不像正规金融教材,也不像常见的“龙头战法”“打板秘籍”。但恰恰是这个组合,暴露了…

2026/10/18 6:59:58 阅读更多 →
SVM缺页处理机制全解析:从NPT到KVM的性能优化实践

SVM缺页处理机制全解析:从NPT到KVM的性能优化实践

SVM(AMD Secure Virtual Machine)是AMD处理器上的虚拟化扩展,而缺页处理机制是虚拟化内存管理里最容易被低估、但实际影响极大的一个环节。很多做虚拟化平台的人把注意力放在调度、中断、IO虚拟化上,等到业务出现诡异的卡顿或者大…

2026/10/18 6:59:58 阅读更多 →
OpenClaw(Clawdbot)本地部署指南:从零构建24小时AI助理与自动化工作流

OpenClaw(Clawdbot)本地部署指南:从零构建24小时AI助理与自动化工作流

1. 从“一周只工作2天”说起:我为什么认真琢磨起了AI助理先别急着把“未来一周只工作2天”当成一个段子。作为一个常年被日报、周报、数据汇总、消息回复淹没的从业者,我第一次听到这个观点时也觉得是专家的嘴骗人的鬼。但当我花了几个周末,把…

2026/10/18 6:58:58 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →