1734. Decode XORed Permutation:利用 XOR 自反性还原奇数长度排列(LeetCode-Go 解法全解)
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
导读
本题(1734. Decode XORed Permutation 仓库中的实现为主线,从 XOR 的三个基本性质出发,推导出如何借助"全集异或"与"奇数位异或"先锁定perm[0],再沿编码链逐位还原整个数组,并给出复杂度分析与可运行的测试验证,帮助读者掌握一类"已知相邻差分还原序列"的位运算通解。
一、题目回顾与核心难点
1.1 题意重述
有一个整数数组perm,它是前n个正整数的排列(即1, 2, ..., n各出现一次),且n恒为奇数。
它被编码为长度为n - 1的数组encoded,编码规则为:
encoded[i] = perm[i] XOR perm[i + 1]例如perm = [1, 3, 2]时,encoded = [1 XOR 3, 3 XOR 2] = [2, 1]。
题目保证答案存在且唯一,给定encoded,要求还原出原始数组perm。
1.2 约束条件
3 <= n < 10^5n是奇数encoded.length == n - 1
1.3 示例
示例 1:
Input: encoded = [3,1] Output: [1,2,3]验证:perm = [1,2,3]时,encoded = [1 XOR 2, 2 XOR 3] = [3,1]。
示例 2:
Input: encoded = [6,5,4,6] Output: [2,4,1,5,3]1.4 核心难点
表面上看,n - 1个方程、n个未知数,似乎信息不足。真正让题目可解的关键有两点:
- 排列的封闭性:
perm是前n个正整数的排列,意味着perm[0] XOR perm[1] XOR ... XOR perm[n-1]等于1 XOR 2 XOR ... XOR n,这个值不需要知道排列顺序也能直接算出来; - n 为奇数的结构性保证:
n是奇数,才能让"编码数组奇数下标的异或"恰好覆盖perm[1]到perm[n-1]的全部元素,从而构造出total ^ odd消去冗余、单点定位perm[0]。
二、算法基石:XOR 的三大性质
本题整个解题过程只依赖异或运算的三条性质:
| 性质 | 表达式 | 说明 |
|---|---|---|
| 自反性 | x XOR x = 0 | 相同值异或结果为 0 |
| 恒等性 | x XOR 0 = x | 与 0 异或保持不变 |
| 交换律与结合律 | a XOR b = b XOR a,(a XOR b) XOR c = a XOR (b XOR c) | 异或顺序不影响结果 |
其中自反性是本题的核心武器:任何出现偶数次的元素会在整体异或中互相抵消。这与仓库中 136. Single Number 的解法 一脉相承——第 136 题正是利用result ^= nums[i]让成对出现的数字全部抵消、留下唯一落单者。原文档解题思路部分也明确指出:"这一题与第 136 题和第 137 题思路类似,借用x ^ x = 0这个性质解题。"
三、推导过程:如何锁定 perm[0]
3.1 第一步:计算全集异或 total
由于perm是1 ~ n的排列,无论顺序如何,全部元素异或的结果恒等于:
total = 1 XOR 2 XOR 3 XOR ... XOR n这一步不需要知道排列,只需要知道n,时间复杂度为 O(n)。
3.2 第二步:计算奇数下标异或 odd
考察encoded中奇数下标的元素:
odd = encoded[1] XOR encoded[3] XOR ... XOR encoded[n-2]由于encoded[i] = perm[i] XOR perm[i+1],把上式展开:
odd = (perm[1] XOR perm[2]) XOR (perm[3] XOR perm[4]) XOR ... XOR (perm[n-2] XOR perm[n-1])注意:n是奇数,所以encoded的长度n - 1是偶数,其奇数下标取值为1, 3, ..., n-2,恰好覆盖了perm[1]到perm[n-1]的全部元素。这就是题目保证 n 为奇数的意义所在——若不是奇数,这一步就无法精确覆盖"除 perm[0] 外的所有元素"。
3.3 第三步:total XOR odd 得到 perm[0]
将 total 与 odd 异或:
total XOR odd = (perm[0] XOR perm[1] XOR ... XOR perm[n-1]) XOR (perm[1] XOR perm[2] XOR ... XOR perm[n-1]) = perm[0] // 其余元素成对出现,经 x XOR x = 0 全部抵消perm[1] ~ perm[n-1]在两个集合中各出现一次,异或后相互抵消归零,只剩下perm[0]。由此成功"锚定"第一个原始元素。
3.4 第四步:沿编码链递推还原全部元素
拿到perm[0]后,问题就变成了简单的链式递推。因为encoded[i] = perm[i] XOR perm[i+1],两边同时异或perm[i]:
perm[i+1] = perm[i] XOR encoded[i]逐层推导:
encoded[0] = perm[0] XOR perm[1] perm[0] XOR encoded[0] = perm[0] XOR (perm[0] XOR perm[1]) = perm[1] perm[1] XOR encoded[1] = perm[1] XOR (perm[1] XOR perm[2]) = perm[2] ... perm[n-2] XOR encoded[n-2] = perm[n-1]依次类推,即可还原出原数组perm中的所有数。
四、仓库源码逐行剖析
4.1 核心实现
原文档给出的解法与仓库中的实际源码完全一致,位于 1734. Decode XORed Permutation.go:
package leetcode func decode(encoded []int) []int { n, total, odd := len(encoded), 0, 0 for i := 1; i <= n+1; i++ { total ^= i } for i := 1; i < n; i += 2 { odd ^= encoded[i] } perm := make([]int, n+1) perm[0] = total ^ odd for i, v := range encoded { perm[i+1] = perm[i] ^ v } return perm }逐段解读:
| 代码段 | 作用 | 细节说明 |
|---|---|---|
n, total, odd := len(encoded), 0, 0 | 初始化 | n即原始数组长度len(perm),因为encoded长度为n-1,所以perm长度为n+1 |
for i := 1; i <= n+1; i++ { total ^= i } | 计算全集异或 | total = 1 XOR 2 XOR ... XOR (n+1),对应原始文档中的[1, n+1]区间 |
for i := 1; i < n; i += 2 { odd ^= encoded[i] } | 计算奇数下标异或 | 步长为 2,遍历encoded[1], encoded[3], ... |
perm := make([]int, n+1) | 分配结果数组 | 长度为n+1,恰好等于len(encoded) + 1 |
perm[0] = total ^ odd | 锚定首元素 | 见上文 3.3 节推导 |
for i, v := range encoded { perm[i+1] = perm[i] ^ v } | 链式递推 | 利用perm[i+1] = perm[i] XOR encoded[i]逐个还原 |
return perm | 返回结果 | 直接返回,无需额外处理 |
4.2 边界情况与正确性说明
- n 最小为 3:当
n = 3时,encoded长度为 2,total = 1 XOR 2 XOR 3 = 0,odd = encoded[1],perm[0] = 0 XOR encoded[1] = encoded[1],随后perm[1] = perm[0] XOR encoded[0]、perm[2] = perm[1] XOR encoded[1],逻辑依然成立; - 答案唯一性:题目保证答案存在且唯一,因此在
3 <= n < 10^5的约束下,上述推导不会出现歧义; - 无需排序:整个算法不依赖对
perm排序,仅靠位运算性质完成还原,这是相对朴素做法的最大优势。
五、测试用例与运行验证
仓库为本题配套了完整的单元测试,位于 1734. Decode XORed Permutation_test.go,覆盖了题目给出的两个示例:
package leetcode import ( "fmt" "testing" ) type question1734 struct { para1734 ans1734 } // para 是参数 // one 代表第一个参数 type para1734 struct { encoded []int } // ans 是答案 // one 代表第一个答案 type ans1734 struct { one []int } func Test_Problem1734(t *testing.T) { qs := []question1734{ { para1734{[]int{3, 1}}, ans1734{[]int{1, 2, 3}}, }, { para1734{[]int{6, 5, 4, 6}}, ans1734{[]int{2, 4, 1, 5, 3}}, }, } fmt.Printf("------------------------Leetcode Problem 1734------------------------\n") for _, q := range qs { _, p := q.ans1734, q.para1734 fmt.Printf("【input】:%v 【output】:%v\n", p, decode(p.encoded)) } fmt.Printf("\n\n\n") }测试用例逐一手动推演验证:
用例一:encoded = [3, 1],期望[1, 2, 3]
n = 2(encoded 长度),perm长度= 3;total = 1 XOR 2 XOR 3 = 0;odd = encoded[1] = 1;perm[0] = 0 XOR 1 = 1;perm[1] = 1 XOR 3 = 2,perm[2] = 2 XOR 1 = 3;- 得到
[1, 2, 3],正确。
用例二:encoded = [6, 5, 4, 6],期望[2, 4, 1, 5, 3]
n = 4,perm长度= 5;total = 1 XOR 2 XOR 3 XOR 4 XOR 5 = 1;odd = encoded[1] XOR encoded[3] = 5 XOR 6 = 3;perm[0] = 1 XOR 3 = 2;- 递推:
perm[1] = 2 XOR 6 = 4,perm[2] = 4 XOR 5 = 1,perm[3] = 1 XOR 4 = 5,perm[4] = 5 XOR 6 = 3; - 得到
[2, 4, 1, 5, 3],正确。
读者可以在仓库根目录通过go test ./leetcode/1734.Decode-XORed-Permutation/运行该测试,验证实现与期望输出一致。
六、复杂度分析与正确性论证
6.1 时间复杂度
整个算法共有四段线性遍历:
- 计算
total:O(n); - 计算
odd:O(n/2); - 递推还原
perm:O(n)。
总时间复杂度为O(n),在n < 10^5的约束下非常高效。与 LeetCode-Go 项目对单题解法"runtime beats 100%"的目标定位一致(具体性能表现以实际评测为准,本文不引用未经证实的性能数据)。
6.2 空间复杂度
只额外分配了结果数组perm,长度为n + 1,辅助变量为常数个,因此空间复杂度为O(n)(结果数组本身不计入辅助空间时为 O(1) 额外空间,视 LeetCode 判题约定而定)。
6.3 为什么 n 必须是奇数——一个反例推演
为帮助读者理解题目条件,这里做一个反例推演。假设n = 4(偶数),则encoded长度为 3,奇数下标只有encoded[1]:
odd = perm[1] XOR perm[2]此时odd只覆盖了perm[1], perm[2],并未覆盖perm[3]。计算total XOR odd:
(perm[0] XOR perm[1] XOR perm[2] XOR perm[3]) XOR (perm[1] XOR perm[2]) = perm[0] XOR perm[3]得到的不是单一元素,无法锚定perm[0]。这就是题目强制n为奇数的根本原因:只有奇数长度才能保证"奇数下标编码"与"去掉 perm[0] 的剩余元素集合"一一对应。
七、同源思路:LeetCode-Go 中的 XOR 系列问题
本题的核心思想——"用整体异或抵消成对元素"——在 LeetCode-Go 仓库中是一个反复出现的模式,可以串联学习:
| 题号 | 题目 | 与本题的关系 |
|---|---|---|
| 136. Single Number | 找出唯一落单元素 | 最基础的x XOR x = 0应用:全部异或即得答案 |
| 137. Single Number II | 找出只出现一次的元素(其余出现 3 次) | 在自反性基础上引入按位统计的进阶变形 |
| 1734. Decode XORed Permutation | 还原 XOR 相邻差分排列 | 在全集异或基础上,进一步利用"奇数下标编码覆盖剩余集合"构造锚点 |
原文档解题思路明确指出本题与第 136、137 题思路类似。第 136 题的源码(136. Single Number.go)只用了一个循环:
func singleNumber(nums []int) int { result := 0 for i := 0; i < len(nums); i++ { result ^= nums[i] } return result }对比可见:第 136 题是"全集异或"的零成本应用(因为目标元素天然唯一);而第 1734 题由于需要还原整个序列,必须在全集异或之外再构造一个"剔除锚点元素"的集合,这正是odd存在的意义。理解这一层递进关系,有助于在面试中面对"差分 + 排列"类问题时快速定位解题方向。
八、方法总结与扩展思考
8.1 解题方法论沉淀
- 遇到"相邻差分"类还原问题,先检查是否具备"全集封闭"特性(如本题的排列性质),若有则可直接计算出全集异或;
- 利用奇偶位置构造子集,使子集与"全集去掉某个元素"形成精确互补,从而用
total XOR subset单点定位; - 锚定一个元素后,其余元素可沿差分链线性递推,无需任何比较或排序操作;
- 整个过程可以总结为一句口诀:"全集异或除锚点,锚点异或差分还原"。
8.2 变体与扩展
- 若题目改为给定
encoded与perm[0]的值,则total与odd都不再需要,直接沿链递推即可,问题降级为纯 O(n) 遍历; - 若
perm不是排列而是任意数组,则缺少"全集封闭"条件,本题的 O(n) 解法不再成立,需要其他约束; - 位运算解法的可读性依赖对 XOR 性质的熟悉程度,在代码评审或面试讲解中,建议先口述"成对抵消"的直观含义,再展示代码。
8.3 在 LeetCode-Go 仓库中阅读本题的路径
- 题目文档:leetcode/1734.Decode-XORed-Permutation/README.md
- 核心实现:1734. Decode XORed Permutation.go
- 单元测试:1734. Decode XORed Permutation_test.go
- 关联基础题:136. Single Number、137. Single Number II
结语
1734 题是"位运算 + 排列结构"结合的经典题目:n为奇数的条件、total ^ odd的锚点构造、以及沿编码链的线性递推,三步环环相扣,最终得到一个 O(n) 时间、O(n) 空间的优雅解法。掌握本题,不仅能应对同类"相邻 XOR 差分还原"问题,更能加深对异或自反性在算法设计中应用的理解——这也是 LeetCode-Go 仓库将其作为 XOR 系列重要一环的价值所在。
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考