题目

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。
格雷编码序列必须以 0 开头。

示例1

  1. 输入: 2
  2. 输出: [0,1,3,2]
  3. 解释:
  4. 00 - 0
  5. 01 - 1
  6. 11 - 3
  7. 10 - 2
  8. 对于给定的 n,其格雷编码序列并不唯一。
  9. 例如,[0,2,3,1] 也是一个有效的格雷编码序列。
  10. 00 - 0
  11. 10 - 2
  12. 11 - 3
  13. 01 - 1

实现

思路1 回溯法

由题目得我们需要对二进制编码的可能性进行搜索,并且相邻的数值仅能有一个位数的差异,也就是变换二进制编码时每次只能修改一个位数。因此我们可以把不同的二进制编码看作状态,初始状态设为全部为0的二进制数,例如n=3时初始状态为000,那么第一步对初始状态进行变换就只有n=3种可能(001010100这三种状态)。但具体选择哪个状态我们不能确定,可以先选择其中一条路径测试,若不是后面再退回到这一步选择另外的路径测试。
上面的思路用编程表达其实就是回溯法,循环终止的条件就是 树深depth == 89. 格雷编码 - 图1。此时从根到叶节点的路径上的每个节点,就是格雷编码。
由于假设001后会再生成101, 011, 000三种状态,而000与选择过的根节点相同,我们不能再选,所以这里定义一个hashset记录已经选过的数值。

  1. class Solution:
  2. def grayCode(self, n: int) -> List[int]:
  3. visit = set([0]) #先加入根节点的状态
  4. def backtrack(path):
  5. if len(path) == 2 ** n:
  6. return path
  7. for i in range(n):
  8. # 对上一个状态的每个数字变换
  9. transform = 1 << i ^ path[-1] # 用异或
  10. if transform in visit:
  11. continue
  12. visit.add(transform)
  13. # 先选择这个状态
  14. path.append(transform)
  15. if backtrack(path): # 若这个path是错的,最后会为空
  16. return path # 只有对的才会进入
  17. path.pop()
  18. visit.remove(transform)
  19. return backtrack([0])

时间复杂度:89. 格雷编码 - 图2
空间复杂度:89. 格雷编码 - 图3
**

思路2 镜像法

该方法源自别人的题解,需要对格雷编码有一定了解,可以发现以下规律:

  1. 给 G(n) 阶格雷码每个元素二进制形式前面添加 0,得到 G’(n);
  2. 设 G(n) 集合倒序(镜像)为 R(n),给 R(n) 每个元素二进制形式前面添加 1,得到 R’(n);
  3. 拼接上述两个集合89. 格雷编码 - 图4得到下一阶格雷码。

编程实现以上规律:

  1. class Solution:
  2. def grayCode(self, n: int) -> List[int]:
  3. res, head = [0], 1
  4. for i in range(n):
  5. for j in range(len(res) - 1, -1, -1):
  6. res.append(head + res[j])
  7. head <<= 1
  8. return res

参考
https://leetcode-cn.com/problems/gray-code/solution/gray-code-jing-xiang-fan-she-fa-by-jyd/