一、题目
n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
第一个整数是 0
一个整数在序列中出现 不超过一次
每对 相邻 整数的二进制表示 恰好一位不同 ,且
第一个 和 最后一个 整数的二进制表示 恰好一位不同
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
二、思路解析
1.直接思考——回溯法实现
先是根据确定数的范围,然后是输出满足条件的数,这个数可以存储在数组中,也是可以存储在列表中的。
可以用回溯法实现,但是速度有点慢。
2.找到各个数产生的规律
关键是搞清楚格雷编码的生成过程, G(i) = i ^ (i/2);
如 n = 3:
G(0) = 000,
G(1) = 1 ^ 0 = 001 ^ 000 = 001
G(2) = 2 ^ 1 = 010 ^ 001 = 011
G(3) = 3 ^ 1 = 011 ^ 001 = 010
G(4) = 4 ^ 2 = 100 ^ 010 = 110
G(5) = 5 ^ 2 = 101 ^ 010 = 111
G(6) = 6 ^ 3 = 110 ^ 011 = 101
G(7) = 7 ^ 3 = 111 ^ 011 = 100
3.二进制数转格雷编码
不管n为几,当前n的格雷码中的前一半始终为n - 1的全部,所以这时我们可以忽略n在格雷码中的影响
三、知识点
1.常见的几种位运算
“我们知道程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
在系统软件中,常常需要处理二进制位的问题。C语言提供了6个位操作运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型。”
要进行位运算,先是要转换为二进制。
1)取反
0变1,1变0
2)与
0变1,1变0
3)或
两位同时为0,结果才为0,否则结果为1
4)异或
相同取0,不同取1
异或的几条性质
5)右移
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
6)左移
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)
7)无符号右移
始终补0,不考虑正负数。
8)符号和描述
四、代码
1.回溯法java
class Solution {
List<Integer> ans = new ArrayList<>();
int limit;
boolean flag = false;
int n;
public List<Integer> grayCode(int n) {
this.n = n;
limit = (int) Math.pow(2, n);
int[] map = new int[limit];
map[0] = 1;
ans.add(0);
backtrack(0, map);
return ans;
}
private void backtrack(int current, int[] map) {
if (ans.size() == limit) {
if (Integer.bitCount(current) == 1) flag = true;
return;
}
for (int i = 0; i < n; i++) {
int next = current ^ (1 << i);
if (map[next] == 0) {
map[next] = 1;
ans.add(next);
backtrack(next, map);
if (flag) return;
map[next] = 0;
ans.remove((Integer) next);
}
}
}
}
2.找规律java
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> ret = new ArrayList<>();
for(int i = 0; i < 1<<n; ++i)//移位操作
ret.add(i ^ i>>1);//先是将i每一位向后移动一位,然后进行异或操作
return ret;
}
}
3.二进制转格雷编码C++
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> ret(1 << n);
for (int i = 0; i < ret.size(); i++) {
ret[i] = (i >> 1) ^ i;
}
return ret;
}
};
4.二进制转为格雷编码java
class Solution {
public List<Integer> grayCode(int n) {
List<Integer> ret = new ArrayList<Integer>();
for (int i = 0; i < 1 << n; i++) {
ret.add((i >> 1) ^ i);
}
return ret;
}
}
五、总结
1.二进制转换为格雷编码的复杂度分析
时间复杂度:O(2^n)
其中 n为格雷码序列的位数。每个整数转换为格雷码的时间复杂度为 O(1),总共有 2^n个转换。
空间复杂度:O(1)。注意返回值不计入空间复杂度。