做这类构造题最怕一上来就盯着“怎么把数组填出来”结果被各种输出限制绕晕。LeetCode 3315《构造最小位运算数组 II》这道每日一题输入是一个数组输出也要求一个数组核心却不在数组本身而在每一位数字背后的二进制规律。我最早接触它的时候第一反应是枚举后来发现这题真正的考点是你能不能从x | (x1)这个操作的结果反推出最小的 x。这篇文章我会从题目拆解、位运算原理、代码实现、边界情况、对拍验证这几个角度完整讲一遍。无论是刚刷题的新手还是想快速复习位运算套路的老手都能在文章里找到可以直接抄走的东西。1. 题目到底在问什么先看懂x | (x1)的脾气1.1 一个数组输入、数组输出的构造题先明确题面给定一个非负整数数组nums对数组里的每一个数字nums[i]你需要构造出一个最小的非负整数x使得x | (x1) nums[i]成立。如果这样的x不存在对应位置返回-1。也就是说输出数组的每一位都是根据输入数组对应位的那个数字反推出来的。输入[1, 3, 4]输出可能是[0, 1, -1]因为0 | 1 11 | 2 3而找不到一个非负整数x能让x | (x1) 4。构造题有个特点它不像动态规划或图论那样需要状态转移而是需要你找到一种“生成规则”。这题的关键就是彻底看懂x | (x1)这个位运算操作到底对二进制的哪一位做了什么事情。1.2 拆解x | (x1)的底层行为先拿几个小数字做实验x 0二进制是0x 1 10 | 1 1x 1二进制是1x 1 2二进制是1001 | 10 11也就是3x 4二进制是100x 1 5二进制是101100 | 101 101也就是5x 7二进制是111x 1 8二进制是10000111 | 1000 1111也就是15观察二进制的人会立刻发现一件事x和x1是连续的整数它们除了最低那一段 1 会产生进位之外更高位基本保持不变。异或运算能把两个数的差异位标出来或运算则会把进位后新出现的 1 和原来低位的 1 全部保留。所以x | (x1)的结果本质上就是一个“给最低位连续的 1 段向右扩展一位”的操作。如果x 10111它的低位连续 1 段长度是 3那么x | (x1)得到的结果低位 1 段长度会变成 4也就是101111的一部分。1.3 用例子把规律钉死为了确保规律没有偏差我列一张对照表把x、x1、以及x | (x1)的关系摆在一起看xx 的二进制x1 的二进制x(x1)结果二进制001111110311410010151017111100015111191001101011101111101111001511111910011101002310111看到最后几行你应该有感觉了结果的低位连续 1 段长度永远比x的低位连续 1 段长度多 1。这是整个题目解题的钥匙。2. 从结果反推无解判定与答案构造公式2.1 为什么偶数一定无解先回答一个最直观的问题什么样的n一定不存在对应的x因为x和x1是连续的两个整数它们一奇一偶。奇数二进制最低位是 1偶数二进制最低位是 0。按位或之后最低位只要有任意一个 1结果就是 1。所以无论x是奇数还是偶数x | (x1)的结果最低位一定是 1。换句话说输入的n如果是个偶数最低位是 0那这个n根本就不可能是某个x | (x1)的结果。这种情况直接返回-1就是正确答案。这个结论非常简洁但它省掉了大量无效的枚举。我在第一次做这道题时就是先写了一个暴力循环发现所有偶数都会在枚举到n之前失败后来才意识到这不是偶然而是位运算本身的必然。2.2 低位连续 1 段长度 m 是唯一需要的东西假设输入的n是奇数那么它从最低位开始有一段连续的 1。比如n 11二进制是1011最低位连续的 1 段长度是 2也就是从第 0 位和第 1 位都是 1第 2 位是 0比如n 7二进制是111低位连续 1 段长度是 3而第 3 位是 0或者说第 3 位已经超出二进制当前位数可以认为它前面有一个隐藏的 0。这个连续 1 段的长度就是题目反推的核心变量。设它为m那么根据前面的规律原始x的低位连续 1 段长度应该是m - 1也就是说x比n少了一个 1而这个 1 是出现在“连续 1 段最高位”的。举例来说n 1011低位连续 1 段长度是 2那x的低位连续 1 段长度就是 1。n里第 1 位是 1x里第 1 位就必须变成 0更高位保持不变于是x 1001 9。再验证n 7低位连续 1 段长度是 3x的低位连续 1 段长度就是 2n里第 2 位是 1x里第 2 位变成 0所以x 011 3。2.3 统一公式把连续 1 段的最高位清 0于是答案公式变得非常简单对于奇数n先求出它从最低位开始的连续 1 段长度m然后把n的第m - 1位也就是这段连续 1 的最高位从 1 变成 0其他位全部不动得到的就是最小的x。写成位运算就是x n ^ (1 (m - 1))因为n的第m - 1位本来就是 1用异或可以把这一位翻转成 0而其他位不受影响。这里有一个容易绕晕的点为什么是“把连续 1 段的最高位清 0”而不是“把第 m 位变成 1”我一开始也犯过这个错。原因是x | (x1)的结果里低位连续 1 段的长度虽然是m但多出来的那个 1 不是靠“把n的第 m 位变成 1”得到的。n的第 m 位本身是 0而结果里的这个 1 来自进位原本在x的第 m 位那里是一个 0。真正发生变化的位置是x的“连续 1 段最高位”它会被x1的进位清成 0同时把下一个 0 位变成 1再进行或运算后低位原有的 1 全部保留。反推的时候只需要把n里这段连续 1 的最高位还原成 0其他位和n保持一样就行。所以核心只有一句话结果的低位连续 1 段长度比原数的低位连续 1 段长度多 1。反推就是把这个多余的 1 去掉。3. 代码实战从暴力枚举到 O(1) 构造3.1 用暴力验证公式的正确性在写最终代码之前我建议先写一个暴力版本方便对拍验证。对于一个给定的n直接从小到大枚举所有可能的x检查x | (x1)是否等于n。因为答案一定小于等于n所以枚举范围控制在0到n就够。def brute(n: int) - int: for x in range(n 1): if (x | (x 1)) n: return x return -1这个暴力方法在n很小时完全没有问题可以用来验证后面的公式是否正确。我在本地拿1到10000全部跑了一遍公式和暴力的结果完全一致。暴力的作用不是用来提交而是用来建立信心。公式题最怕的就是自我感动式推导写个对拍器一测所有边界情况都暴露了。3.2 C 参考实现有了公式正式代码就很短了。这里要处理一个关键步骤如何求n的低位连续 1 段长度m。最稳妥的方法是循环右移每遇到一个 1 就累加遇到 0 或右移为 0 时停止。#include vector using namespace std; class Solution { public: vectorint minBitwiseArray(vectorint nums) { vectorint ans; ans.reserve(nums.size()); for (int n : nums) { if ((n 1) 0) { ans.push_back(-1); continue; } int m 0; int t n; while (t 1) { m; t 1; } ans.push_back(n ^ (1 (m - 1))); } return ans; } };循环的次数最多也就是二进制位数比如 32 位整数最多循环 31 次。对于单个数字来说可以看作 O(1)整个数组的时间复杂度是 O(nums.size())。空间复杂度是 O(1)除了返回结果之外没有额外的大结构。3.3 Python 和位运算的细节差异Python 版本的逻辑完全一样但有一个非常容易踩的坑Python 的整数没有固定位数右移不会自动归零处理。好在这里我们只关心最低位的连续 1所以逻辑依然简单。def construct(nums): ans [] for n in nums: if n % 2 0: ans.append(-1) continue m 0 t n while t 1: m 1 t 1 ans.append(n ^ (1 (m - 1))) return ansPython 里需要注意n 1和n % 2对正整数来说等价但前者更贴近位运算语义。另外1 (m - 1)在m为 0 时会变成1 -1这是会报错的。但m为 0 只发生在n是偶数时而那段在判断偶数时已经continue掉了所以这里的m必然大于等于 1。C 里如果用__builtin_ctz这类内置函数代码可以更短但有一个隐藏的风险我放到下一节说。先记住一个原则在竞赛中简单清晰的循环永远不会错炫技式的内建函数反而可能让你在边界上翻车。4. 我踩过的坑边界条件全集4.1 最小输入和全 1 输入先看n 1的情况。n是奇数低位连续 1 段长度m 1代入公式ans 1 ^ (1 0) 1 ^ 1 0验证一下0 | 1 1而且0是最小的非负整数所以答案正确。这个例子很容易被忽略但它恰好验证了“最小的 x 可以是 0”。再看全 1 输入比如n 7、n 15、n 31。这些数字的二进制全是 1循环求m时会一直右移到t变成 0 才停下。比如n 7二进制是111t依次是111、11、1、0循环次数是 3得到m 3。答案7 ^ (1 2) 7 ^ 4 3验证3 | 4 7正确。所以全 1 输入不需要单独判断循环版本天然能处理。但如果你用某些内置函数就要格外小心因为全 1 数字的反码可能全是 0导致内置函数行为未定义。4.2__builtin_ctz的未定义行为陷阱有的题解会写成这样int m __builtin_ctz(~n); ans.push_back(n ^ (1 (m - 1)));ctz是 count trailing zeros统计二进制末尾连续 0 的个数。对奇数n来说~n的末尾连续 0 个数恰好就是n的末尾连续 1 个数所以这个写法理论上成立。但问题在于如果n是 int 类型中的全 1也就是-1那么~n 0ctz(0)是未定义行为。虽然在 LeetCode 的测试里不一定碰到-1但这种写法有明显的隐患。另一个坑是~n在高位会变成 1。对于形如n 7的情况~n在 32 位 int 中其实是11111111111111111111111111111000末尾连续 0 的个数是 3这没错。但如果n本身是0x7FFFFFFF这种 31 位全 1 的正数~n的末尾连续 0 个数会变成 31而按题意我们应该把第 30 位改成 0两个结论就冲突了。所以我的建议是别在正式代码里用__builtin_ctz处理这个题老老实实写 while 循环。4.3 位运算优先级和类型转换另一个容易出问题的地方是运算符优先级。比如ans.push_back(n ^ (1 (m - 1)));这里的右移和左移都要用括号包起来尤其1 (m - 1)不能写成1 m - 1。在 C 里的优先级低于加减法所以1 m - 1实际上会先算m - 1看起来结果一样其实 C 里移位运算符优先级比加减法低所以1 m - 1等于1 (m - 1)这个例子反而没问题。但为了可读性和防止在别的语言里翻车我习惯所有位运算都加括号。Python 里的优先级更反直觉的优先级也低于加法减法但高于比较运算符。如果不加括号代码很难一眼读对。我的原则是位运算和算术运算混在一起时一律用括号标明顺序。还有类型转换问题。LeetCode 的输入范围通常不超 int但如果你把1 (m - 1)用在超出 int 范围的场景要考虑用1LL转成 long long。这道题正常不会需要但养成习惯没坏处。4.4 一组特殊输入速查表我整理了一组测试用例建议提交前全部跑一遍输入 n低位连续 1 长度 m答案 x验证 x(x1)11001 12偶数-1无32112 34偶数-1无51445 573334 791889 91129910 11131121213 13154778 15233191920 23有了这个表大部分边界情况都能覆盖到。5. 测试与对拍怎么确保答案真的最小5.1 写一个独立对拍器公式题最怕的不是思路错而是“局部对但整体错”。所以我每次写完公式解都会同步写一个暴力解然后让它们随机对拍。对拍器的逻辑很简单生成随机测试数据同时跑暴力版本和公式版本逐个比较结果一旦不一致就打印出来。下面是我用的 Python 对拍脚本import random def brute(n): for x in range(n 1): if (x | (x 1)) n: return x return -1 def fast(n): if n % 2 0: return -1 m 0 t n while t 1: m 1 t 1 return n ^ (1 (m - 1)) for n in range(1, 20000): if brute(n) ! fast(n): print(fmismatch: {n}, brute{brute(n)}, fast{fast(n)}) break else: print(all ok)这里没有用随机数据而是直接从 1 到 19999 全覆盖。因为范围不大暴力也跑得动。这样测试比纯随机更全面不会漏掉少数边界。5.2 用公式再反向验证除了和暴力对拍还可以做一层反向验证对公式算出来的每个x重新计算x | (x1)确认它等于输入的n并且确认x小于等于n。这一步能抓住“构造出的数组满足条件”这个最基本的要求。反向验证本质上是在做性质测试。刷题时我习惯在本地写这么一段def verify(nums): ans construct(nums) for n, x in zip(nums, ans): if x ! -1: assert (x | (x 1)) n, (n, x) assert x 0 return True只有正向公式、暴力对拍、反向验证三关全过我才会把代码提交。5.3 性能压力测试这道题的时间复杂度很低就算nums有十万个元素每个数字做一次常数级操作也完全不会超时。但如果你在循环里用了笨办法比如对每个n再套一层循环枚举就会出问题。我做了一个简单的性能测试构造一个长度一百万的数组里面随机生成一万以内的奇数偶数然后跑公式版本耗时在毫秒级。这是典型的 O(n) 题目真正的考点从来不是性能而是你能不能把二进制规律想清楚。如果你在面试或者周赛里碰到这题千万不要一上来就写双重循环。先举几个小例子观察规律通常比硬想公式快得多。6. 扩展视角这一题背后通用的位运算套路6.1 由结果反推输入的通用思路这一类题有一个非常通用的模式给你一个操作f(x)再给你操作结果n让你反推满足条件的最小x。解题套路通常是三步。第一步把操作f(x)理解成二进制层面的一次“形态变化”。不要盯着十进制数值看而是把数拆成二进制位看每一位如何变化。第二步找到变化的“不变量”或者“增长规律”。比如这题里低位连续 1 段长度加 1其他位保持不变就是一个非常清晰的不变量。第三步从结果反推输入时只需要把变化的那一步逆回去其他位原样保留。这个方法可以迁移到很多位运算题上比如给定x (x-1)的结果反推x或者给定x ^ (x-1)的结果找 lowbit 规律。位运算题的题面千变万化但底层都是类似的二进制形态变换。6.2 这类“构造最小数组”的题目模式LeetCode 的构造类题目有一个常见套路给你一个目标值要你构造一个结构通常是数组使得某种运算结果等于目标值同时要求结构本身最小。这里的“最小”有不同的定义有时候是数组长度最短有时候是字典序最小有时候是单个数最小。本题就是单个数最小。遇到这种题先别急着套贪心或者 DP先看这个运算本身有没有“可逆性”。如果操作是可逆的比如本题通过连续 1 段长度就可以反推那构造就会非常简单如果操作不可逆比如或运算会丢失信息才需要考虑贪心。还有一个经验构造题里出现“最小”两个字答案往往和一个边界情况有关。本题的最小值是 0因为x可以是 0如果你推导出的最小候选值一直是正数要回头检查是不是漏了 0 的情况。6.3 系列题“II”带来的难度变化题目标注了“II”意味着前面大概率有一个“I”。系列题的升级方式通常有三种数据范围变大、约束变复杂、从单点查询变成批量查询。3315 这个第二版我推测就是把原来给单个数构造的方式变成了给整个数组批量构造。输入输出都变成数组后题目的难度其实不在于单个数怎么算而在于你需要在每个数上都能快速得出答案不能对每个查询都进行一次重的搜索。所以 O(1) 的反推公式在这种批量场景下就显得尤其重要。如果你之前只做过“I”碰到“II”的时候先别慌。比较一下两版题面找出新增的限制是什么往往比从头想一个全新方案要快得多。我在周赛里遇到过好几次“II”比“I”只是把单次查询改成了多次查询只要把单次 O(1) 的逻辑不变边界处理干净就能顺利通过。这题做到最后我个人最大的体会是位运算的题目不要靠“我感觉应该是这样”去写代码一定要拿纸笔把二进制列出来哪怕从 0 到 15 全部列一遍也不亏。很多规律不是想出来的是看出来的。先写一个能跑的暴力版本再在上面观察规律最后推导公式这个过程本身比这道题的 AC 更有价值。
企业数字化 ERP 产品动态
相关推荐
Opus5驱动的工业官网AI内容产线:钢铁洪流式量产实践 1. 项目概述:当“钢铁洪流”不再只是军事术语,而是一套可落地的AI内容生产流水线“钢铁洪流”这个词,最近在内容圈里炸开了锅——它早就不单指装甲集群突击的战术意象,而是被一线创作者悄悄挪用,形容一种高强度、高密度… · 2026/9/24 21:30:08
从零手写 Vue3 通用分页组件:状态管理与页码计算全解析 分页组件是后台管理系统里出现频率最高的组件之一,但很多项目写着写着就乱套了:新页面复制一份老代码,改一改参数,再遇到地方就再复制一份。等到后端接口从 currentPage 变成 pageNum,前端就得满项目找人肉替换。这篇文… · 2026/9/24 21:30:08
Windows下RAR/ZIP/7z密码破解实战:算法选型与硬件优化指南 1. 这不是“黑客教程”,而是一次密码安全认知的实战复盘 你搜“RAR密码移除”“ZIP解压密码清除工具”点进来的,大概率正被一个带密码的压缩包卡住——可能是同事发来的项目资料、下载的开源硬件资料包(比如那个“sw6206原厂方案.rar”&… · 2026/9/24 21:30:08
喷码缺陷检测实战:从数据标注到模型量化部署的完整链路 简介:这份资源是面向高校学生与机器学习入门者的喷码缺陷检测完整项目源码,可直接用于毕业设计、课程设计或期末大作业。项目以Python实现,围绕工业喷码字符的缺陷识别展开,涵盖数据预处理、模型训练与评估等环节,适合… · 2026/9/24 22:01:14
OpenClaw国产化部署实战:模型替换、飞书接入与高频报错排查 先说结论:OpenClaw 能跑,但离“开箱即用”还有一段距离。过去两周我集中调研了 OpenClaw 在国内的真实使用情况,从部署安装、模型配置到消息渠道接入,前后翻了几十份 issue 和配置案例,也找了几位正在跑生产环境的朋友… · 2026/9/24 22:01:14
孪生网络实战:点选验证码识别从数据集到部署 简介:本资源是一套基于孪生神经网络实现点选识别验证码的完整项目源码,面向计算机、人工智能、通信工程等专业的在校学生与教师,也适合具备一定Python基础、希望进阶深度学习实战的开发者,可用于毕业设计、课程设计、作业或项目初… · 2026/9/24 22:01:14
WorkBuddy实战:从订单抓取到知识库整理的自动化工作流指南 最近在几个自动化办公和 AI 工具社群里,画风明显变了。以前大家讨论最多的是“你那个需求用哪个模型能跑”,现在更多是“你 WorkBuddy 里是怎么编排的”“这个场景你用的什么 Skill”。WorkBuddy 从一个偏小众的 AI 工作台工具,慢慢变成了不少… · 2026/9/24 22:01:14
LLM应用效果不佳?先别急着换模型,或许该优化你的Harness 先问大家一个问题:你有多久没被“换模型”这三个字勾住魂了?我见过太多团队和个人开发者,从7B换到14B,再换成70B甚至更大,钱和精力烧了一大堆,最后业务指标纹丝不动,回复质量该飘还是飘… · 2026/9/24 22:01:14
训练慢别急改代码:GPU性能体检与瓶颈定位实战指南 训练慢,几乎是每个碰过深度学习的人都绕不过去的一句话。昨天还有同事跑来找我,说YOLOv8训练自己的数据集,一个epoch快一个小时了,loss明明在降,但就是慢得像在爬,问我要不要换backbone、改loss。我拦住了他… · 2026/9/24 22:01:01
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44