1238. 循环码排列
大约 4 分钟
---
1238. 循环码排列
- 标签:位运算、数学、回溯
- 难度:中等
题目链接
题目大意
描述:给你两个整数 和 。你的任务是返回任意一个包含 的排列 ,并且满足:
- 和 的二进制表示形式只有一位不同
- 和 的二进制表示形式也只有一位不同(即首尾也满足相邻条件,形成一个环)
要求:返回满足条件的排列。
说明:
- 。
- 。
示例:
- 示例 1:
输入:n = 2, start = 3
输出:[3,2,0,1]
解释:这个排列的二进制表示是 (11,10,00,01)
所有的相邻元素都有一位是不同的,另一个有效的排列是 [3,1,0,2]- 示例 2:
输入:n = 3, start = 2
输出:[2,6,7,5,4,0,1,3]
解释:这个排列的二进制表示是 (010,110,111,101,100,000,001,011)解题思路
思路 1:格雷码 + 序列旋转
1. 核心思想
题目要求的排列,其实就是**格雷码(Gray Code)**序列。格雷码是一种二进制编码方式,特点是:
- 相邻的两个编码之间只有一位不同(这叫「单步特性」)。
- 对于 位二进制数,格雷码序列包含全部 个编码。
- 首尾编码也只有一位不同(这叫「循环码特性」)。
可以这样形象地理解:想象一个 维立方体的顶点,每个顶点用一个 位二进制数标记。从一个顶点出发,沿着棱走到下一个顶点,每次只能改变一个坐标(即一位二进制数)。格雷码就是一条恰好经过所有顶点一次,最后回到起点的「哈密顿回路」。
格雷码有一个非常简单通用的生成公式:
其中 从 到 , 表示按位异或运算, 表示右移运算。这样生成的序列自然满足相邻只有一位不同的条件。
但题目要求序列以 开头,而标准格雷码是以 开头的。不过格雷码序列有一个重要性质:循环移位后仍然满足相邻只有一位不同的条件(因为序列本身是循环的)。所以我们只需要在标准格雷码中找到 ,然后把序列「旋转」一下,让 成为第一个元素即可。
2. 具体步骤
第 1 步:生成标准格雷码序列
遍历 从 到 ,使用公式 生成每个格雷码,存入数组 。
第 2 步:找到 的位置
在 中查找 的下标 。
第 3 步:旋转序列
将以 开头的序列构造出来:
即将原序列从 处「切断」,后半部分接上前半部分。
结合示例 1 走一遍:
生成标准格雷码:
- :
- :
- :
- :
标准格雷码序列:
找到 的位置 。
旋转:
检查相邻元素二进制:(,,,),每对都只有一位不同,满足要求。
思路 1:代码
class Solution:
def circularPermutation(self, n: int, start: int) -> List[int]:
# 第 1 步:生成标准格雷码序列
gray = []
for i in range(1 << n):
gray.append(i ^ (i >> 1))
# 第 2 步:找到 start 的位置
idx = gray.index(start)
# 第 3 步:旋转序列,让 start 成为第一个元素
return gray[idx:] + gray[:idx]思路 1:复杂度分析
- 时间复杂度:,其中 是位数。需要生成长度为 的格雷码序列(),并在序列中查找 的位置(最坏情况下也是 )。
- 空间复杂度:,需要存储完整的格雷码序列才能进行旋转操作。由于 ,,在合理范围内。