格雷编码
格雷编码的定义
格雷编码我们在大学时期已经了解过了,在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环码或反射码。 [2] 在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生,则计数中可能出现短暂的其它代码(1100、1111等)。在特定情况下可能导致电路状态错误或输入错误。使用格雷码可以避免这种错误。格雷码有多种编码形式。
格雷码(Gray code)曾用过Grey Code、葛莱码、葛兰码、格莱码、戈莱码、循环码、二进制反射码、最小差错码等名字,它们有的是错误的,有的易与其它名称混淆,建议不再使用它们。
格雷编码的码表
自然数 | 自然数的二进制 | 一位格雷码 | 二位格雷码 | 三位格雷码 | 四位格雷码 |
---|---|---|---|---|---|
0 | 0000 | 0 | 00 | 000 | 0000 |
1 | 0001 | 1 | 01 | 001 | 0001 |
2 | 0010 | 11 | 011 | 0011 | |
3 | 0011 | 10 | 010 | 0010 | |
4 | 0100 | 110 | 0110 | ||
5 | 0101 | 111 | 0111 | ||
6 | 0110 | 101 | 0101 | ||
7 | 0111 | 100 | 0100 | ||
8 | 1000 | 1100 | |||
9 | 1001 | 1101 | |||
10 | 1010 | 1111 | |||
11 | 1011 | 1110 | |||
12 | 1100 | 1010 | |||
13 | 1101 | 1011 | |||
14 | 1110 | 1001 | |||
15 | 1111 | 1000 |
LeetCode 89. 格雷编码
LeetCode 89. 格雷编码
n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
第一个整数是 0
一个整数在序列中出现 不超过一次
每对 相邻 整数的二进制表示 恰好一位不同 ,且
第一个 和 最后一个 整数的二进制表示 恰好一位不同
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
实例
输入: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 有一位不同
思路与代码
思路一:找规律
观察格雷码的规律,具有一定的对称性,高位是 1 或者 0,并由此就行对称
代码一
//耗时 6ms
class Solution {
public List<Integer> grayCode(int n) {
int count = 1;
List<Integer> res = new ArrayList<>();
res.add(0);
res.add(1);
while (n-- > 1) {
count <<= 1;
for (int i = count - 1; i >= 0; i--) {
res.add(res.get(i) + count);
}
}
return res;
}
}
因为res.get(i)是循环方式去取值,当n的位数确定后格雷数就已经知道多少了,故可以这样写
代码二
class Solution {
public List<Integer> grayCode(int n) {
Integer[] res = new Integer[1 << n];
res[0] = 0;
res[1] = 1;
int count = 1;
while (n-- > 1) {
count <<= 1;
for (int i = 0; i < count; i++) {
res[i + count] = count + res[count - i - 1];
}
}
return Arrays.asList(res);
}
}
思路二:与自然数之间的关系(你必须知道,这个规律要去百度才知道)
格雷码→二进制码(解码):
从左边第二位起,将每位与左边一位解码后的值异或,作为该位解码后的值(最左边一位依然不变)。依次异或,直到最低位。依次异或转换后的值(二进制数)就是格雷码转换后二进制码的值。
代码一
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> res = new ArrayList<>();
int sum = 1<<n;
for(int i = 0;i < sum;i++){
res.add((i >> 1) ^ i);
}
return res;
}
}
LeetCode 1238. 循环码排列
LeetCode 1238. 循环码排列
给你两个整数 n 和 start。你的任务是返回任意 (0,1,2,…,2^n-1) 的排列 p,并且满足:
p[0] = start
p[i] 和 p[i+1] 的二进制表示形式只有一位不同
p[0] 和 p[2^n -1] 的二进制表示形式也只有一位不同
实例:
实例
输入:n = 2, start = 3
输出:[3,2,0,1]
解释:这个排列的二进制表示是 (11,10,00,01)
所有的相邻元素都有一位是不同的,另一个有效的排列是 [3,1,0,2]
思路与代码
题目与上面的是一样的,所有解决方法也是两种
思路一:找规律
观察格雷码的规律,具有一定的对称性,高位是 1 或者 0,并由此就行对称
代码一
class Solution {
public List<Integer> circularPermutation(int n, int start) {
List<Integer> ans = new ArrayList<>();
Integer[] res = new Integer[1 << n];
res[0] = 0;
res[1] = 1;
int count = 1;
int index = start; // 找到位置, 存在start 为 0,1的问题,故直接赋值过去
while (n-- > 1) {
count <<= 1;
for (int i = 0; i < count; i++) {
res[i + count] = count + res[count - i - 1];
if (res[i + count] == start) {
index = i + count;
}
}
}
for (int i = 0; i < res.length; i++) {
ans.add(res[(index + i) % res.length]);
}
return ans;
}
}
思路二:与自然数之间的关系
要通过观察,格雷数的 ^ 关系,格雷是从0开始的,0^任意数都是本身,然后格雷数每个与前一个变化相差为一,故一直第一个数 ^ 结果就是你想要的
代码一:
class Solution {
public List<Integer> circularPermutation(int n, int start) {
List<Integer> res = new ArrayList<>();
int sum = 1<<n;
for(int i = 0;i < sum;i++){
res.add((i >> 1) ^ i ^ start);
}
return res;
}
}