格雷编码(2022-1-8)
leetcode原题
格雷码:在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环码或反射码。
应用:在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生,则计数中可能出现短暂的其它代码(1100、1111等)。在特定情况下可能导致电路状态错误或输入错误。使用格雷码可以避免这种错误。
题目如下:
n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
- 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
- 第一个整数是 0
- 一个整数在序列中出现 不超过一次
- 每对 相邻 整数的二进制表示 恰好一位不同 ,且
- 第一个 和 最后一个 整数的二进制表示 恰好一位不同
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
示例 1:
输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10] 。00 和 01 有一位不同
01 和 11 有一位不同
11 和 10 有一位不同
10 和 00 有一位不同[0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。
00 和 10 有一位不同
10 和 11 有一位不同
11 和 01 有一位不同
01 和 00 有一位不同
你需要先清楚一下几点:
二进制位运算
解题原理:套公式 时间复杂度 O(2n) 空间复杂度 O(1) )
g
(
i
)
=
b
(
i
+
1
)
⊕
b
(
i
)
,
0
≤
i
<
n
g(i)=b(i+1)⊕b(i), 0≤i<n
g(i)=b(i+1)⊕b(i),0≤i<n
其 中 ⊕ 是 按 位 异 或 运 算 , g ( i ) 和 b ( i ) 分 别 表 示 g 和 b 的 第 i 位 , 且 b ( n ) = 0 其中 \oplus 是按位异或运算,g(i) 和 b(i) 分别表示 g 和 b 的第 i 位,且 b(n)=0 其中⊕是按位异或运算,g(i)和b(i)分别表示g和b的第i位,且b(n)=0
var grayCode = function(n) {
// n决定了二进制的位数
let res = []
for(let i = 0; i < 1 << n; i++){
res.push((i >> 1) ^ i)
}
return res
};
二进制找规律
另一种好理解的思路:
前后对称,直接上图(时间复杂度 O(2n) 空间复杂度 O(1) )
- 格雷编码G(n)前面 2*(i-1)位是在G(n-1)的基础上,在二进制位前面补0!
- 后边的剩下的一半就是将前面的对称过来,把前置位0转换成1即可!
- 拼接 G(n) 和 R(n) 即可得到结果
根据以上规律,我们可以通过之前阶的格雷码推出之后任意阶的格雷码
// 官方题解
var grayCode = function(n) {
const res = [0];
for (let i = 1; i <= n; i++) {
const m = ret.length;
for (let j = m - 1; j >= 0; j--) {
ret.push(ret[j] | (1 << (i - 1)));
}
}
return res;
};
递归
也可以换一种思路,采用十进制表示
n = 2
[0, 1, 3, 2]
n = 3
[0, 1, 3, 2, 6, 7, 5, 4]
可以看出 2 - 6,3 - 7,1 - 5,0 - 4,在对称的位置前后相差4,而4其实是2n-1,也就是22得来的。
那么只需要进行如下操作:
- 拿到上一次的格雷码也就是G(n-1)
- 反转并加上2n-1
- 拼接上G(n-1),即可得到G(n)
// 自己写的
var grayCode = function(n) {
if (n === 0) {
return [0]
}
if (n === 1) {
return [0, 1]
}
let formerGrayCode = grayCode(n - 1);
return [
...formerGrayCode,
...formerGrayCode.reverse().map(v => v + Math.pow(2, n - 1)),
];
}