1238. 循环码排列
昂,好一个格雷码,被雷到了。
拿到题目第一时间就想到了计算机组成原理里面的编码知识,码距为1的循环码,然而这就是记忆的极限了。完全没想起叫格雷码,至于怎么算的,就更不可能记得了。想过DFS去搜,但是不会存储结果,还是默默点开了题解。
格雷码
简单搬运一下格雷码的计算方法和进一步的优化原理。
二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,其余各位与次高位的求法相类似。
首先要知道,格雷码并不完全与其表示的真实数字挂钩,它仅仅是一组码距为1的循环码。现在我们考虑两位的情况,那就是
00
、
01
、
11
、
10
00、01、11、10
00、01、11、10。如果我们不考虑其真值,只考虑一个
0
、
1
、
2
、
3
0、1、2、3
0、1、2、3的顺序,那么你可以发现,哎上述转换规则生效了
11
>
>
1
=
01
11>>1=01
11>>1=01,
01
⊕
11
01\oplus 11
01⊕11=10。所以从
0
−
2
n
−
1
0-2^{n-1}
0−2n−1,依次计算按序的格雷码,然后找到start的位置,把前半段往后放,把start调整到第一位就可以了。
然后二进制嘛,必然有我这种废物想不到的性质。我们发现,
g
r
a
y
(
0
)
⊕
s
t
a
r
t
=
s
t
a
r
t
gray(0)\oplus start=start
gray(0)⊕start=start,且相邻格雷码之间只相差一位二进制,那相邻格雷码都异或start,依然相差一位二进制,满足格雷码的条件。所以原始序列整体异或
s
t
a
r
t
start
start,就可以得到以
s
t
a
r
t
start
start为首的格雷码。
class Solution {
public:
vector<int> circularPermutation(int n, int start) {
int ans=1<<n;
vector<int> result(ans);
for (int i=0; i<ans; i++) {
result[i] = i^(i>>1)^start;
}
return result;
}
};
DFS
没找到C++的递归的题解,只找到一个js的,但自己太菜了,转换过来老有bug。懒得写了,累了,贴一个原版js,有空再研究吧。
/**
* @param {number} n
* @param {number} start
* @return {number[]}
*/
var circularPermutation = function(n, start) {
// 构造的数组长度
const len = Math.pow(2, n)
// 用过的数
const used = new Array(len).fill(false)
used[start] = true
// 当前构造的数组
const path = [start]
// 回溯
function dfs(j) {
// 长度等于待构造数组的长度,说明找到了符合条件的,直接返回
if (j == len) {
return path
}
// 上一位数字
const cur = path[path.length - 1]
// n代表二进制总共有几位,我们遍历选择其中一个位置取反(1 => 0,0 => 1)
for (let i = 0; i < n; i++) {
let next
if (cur & (1 << i)) {
// 位置是1,变成0
next = cur & (~(1 << i))
} else {
// 位置是0, 变成1
next = cur | (1 << i)
}
// 用过了不能再试用
if (used[next]) continue
used[next] = true
// 添加到数组中
path.push(next)
const arr = dfs(j + 1)
// 题目要求找到任意一个排列p即可,所以当返回结果有长度,代表有效,直接return
if (arr.length > 0) {
return path
}
// 回溯,将当前的元素从排列中去除
path.pop()
used[next] = false
}
// 上述遍历中没有提前return,说明当前排列p无法构造,返回空数组
return []
}
// 从第2个数开始构造,需要满足与前一个数的二进制只差1位
return dfs(1)
};