1238.循环码排列
题目描述
给你两个整数 n 和 start。你的任务是返回任意 (0,1,2,…,2n-1) 的排列 p,并且满足:
- p[0] = start
- p[i] 和 p[i+1] 的二进制表示形式只有一位不同
- p[0] 和 p[2^n -1] 的二进制表示形式也只有一位不同
示例 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 <= n <= 16
- 0 <= start < 2n
算法一:格雷码
思路
- 格雷码:在二进制表示中, 任意两个(包括首尾)相邻的数只有一位二进制数不同。
- 二进制码转换成二进制格雷码 ,其方法是保留二进制的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,格雷码其余各位与次高位的求法类似。
- 首先将[0,…, 2n - 1] 这些整数转换成对应的格雷码数组,然后找到 start 在格雷码数组中的位置, 将格雷码数组从该位置开始截取, 再将截取部分拼接到格雷码数组的前面,就得到了答案。
收获
- 学习了格雷码 ,我一开始想到了欧拉路径,但是这道题不只有一种答案,比较简单,就没必要使用欧拉路径。
- 我以为需要先将数字转成二进制码,其实没有,答案也是使用整数,直接用整数进行异或运行即可。
算法情况
- 时间复杂度:O(2n) ,其中 n 为题目给定的整数 n;
- 空间复杂度:O(2n)
代码
class Solution {
public:
vector<int> circularPermutation(int n, int start) {
vector<int> g(1 << n, 0);
int loc = 0;
for(int i=0; i< 1 << n; i++){
// 转为gray
g[i] = i ^ (i >> 1);
// 找到start
if(g[i] == start){
loc = i;
}
}
// 数组的截取与拼接
vector<int> ans;
for(int i=loc; i<(loc+ (1<<n)); i++){
ans.push_back(g[i % (1 << n)]);
}
return ans;
}
};
算法二:转换优化
思路
- gray(0) = 0 ,那么 gray(0) ⊕ start = start ,而 gray(i) 与 gray(i-1) 只有一个二进制位不同, 所以 gray(i)⊕ start 和 gray(i-1)⊕ start 也只有一个二进制位不同。
- 因此, 我们也可以直接将 [0,…, 2n - 1] 这些整数转换为对应的 gray(i)⊕ start ,就可以得到首项为 start 的格雷码排列。
收获
算法情况
- 时间复杂度:O(2n)
- 空间复杂度:O(2n)
代码
class Solution {
public:
vector<int> circularPermutation(int n, int start) {
vector<int> ans(1 << n, 0);
int loc = 0;
for(int i=0; i< 1 << n; i++){
// 转为gray
// 能够使 start 最前面
ans[i] = i ^ (i >> 1) ^ start;
}
return ans;
}
};