n 位格雷码序列 是一个由 2^n
个整数组成的序列,其中:
[0, 2^n - 1]
内(含 0
和 2^n - 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 有一位不同
示例 2:
输入:n = 1
输出:[0,1]
1 <= n <= 16
格雷码是一种特殊的二进制编码,其中相邻的两个数的二进制表示只有一位不同。这种编码在很多场景中都有应用,比如在模拟量到数字量的转换中,可以减少因为信号波动导致的误差。
镜像反射法是构造格雷码的一种经典方法,具体步骤如下:
[0]
i
从 1
到 n
:
0
1
这种方法的直观理解是:每次我们将当前的格雷码序列复制一份并反转,然后在原序列的每个数前添加 0
,在镜像序列的每个数前添加 1
,这样可以保证相邻的两个数只有一位不同。
有一个简单的公式可以将二进制数转换为格雷码:G(i) = i ^ (i >> 1)
,其中 ^
表示异或操作,>>
表示右移操作。
使用这个公式,我们可以直接生成 n
位格雷码序列:
2^n
的数组i
从 0
到 2^n - 1
:
G(i) = i ^ (i >> 1)
G(i)
添加到结果数组中n = 1
的情况步骤 | 操作 | 说明 |
---|---|---|
1 | 初始化 | 初始化格雷码序列为 [0] |
2 | 循环 | 对于 i 从 1 到 n |
3 | 复制并反转 | 复制当前序列并反转,得到镜像序列 |
4 | 添加前缀 | 在原序列的每个数前添加 0 ,在镜像序列的每个数前添加 1 |
5 | 合并 | 将两个序列合并 |
6 | 返回结果 | 返回最终的格雷码序列 |
步骤 | 操作 | 说明 |
---|---|---|
1 | 初始化 | 初始化一个长度为 2^n 的数组 |
2 | 循环 | 对于 i 从 0 到 2^n - 1 |
3 | 计算格雷码 | 计算 G(i) = i ^ (i >> 1) |
4 | 添加结果 | 将 G(i) 添加到结果数组中 |
5 | 返回结果 | 返回最终的格雷码序列 |
以 n = 3
为例,使用镜像反射法构造格雷码序列的过程:
初始序列:[0]
第一次迭代(i = 1
):
[0]
-> [0]
[0]
-> [0]
,[0]
-> [1]
[0, 1]
第二次迭代(i = 2
):
[0, 1]
-> [1, 0]
[0, 1]
-> [0, 1]
,[1, 0]
-> [3, 2]
[0, 1, 3, 2]
第三次迭代(i = 3
):
[0, 1, 3, 2]
-> [2, 3, 1, 0]
[0, 1, 3, 2]
-> [0, 1, 3, 2]
,[2, 3, 1, 0]
-> [6, 7, 5, 4]
[0, 1, 3, 2, 6, 7, 5, 4]
最终结果:[0, 1, 3, 2, 6, 7, 5, 4]
public class Solution {
public IList<int> GrayCode(int n) {
List<int> result = new List<int>();
result.Add(0);
for (int i = 0; i < n; i++) {
int size = result.Count;
for (int j = size - 1; j >= 0; j--) {
result.Add(result[j] | (1 << i));
}
}
return result;
}
}
class Solution:
def grayCode(self, n: int) -> List[int]:
result = [0]
for i in range(n):
size = len(result)
for j in range(size - 1, -1, -1):
result.append(result[j] | (1 << i))
return result
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> result = {0};
for (int i = 0; i < n; i++) {
int size = result.size();
for (int j = size - 1; j >= 0; j--) {
result.push_back(result[j] | (1 << i));
}
}
return result;
}
};
|
和 <<
来高效地构造格雷码序列,避免了字符串操作。|
(按位或)和 ^
(按位异或)操作。n = 0
或 n = 1
的特殊情况,需要特别处理。n
较大时,需要注意可能的整数溢出问题。解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
镜像反射法 | O(2^n) | O(2^n) | 直观易懂,容易实现 | 当 n 较大时,可能导致内存问题 |
二进制转格雷码公式 | O(2^n) | O(2^n) | 实现简单,直接计算 | 需要理解格雷码的数学性质 |
回溯法 | O(2^n) | O(2^n) | 可以生成所有可能的格雷码序列 | 实现复杂,效率较低 |