格雷编码
- https://leetcode.cn/problems/gray-code/
描述
- n 位格雷码序列 是一个由 2n2^n2n 个整数组成的序列,其中:
- 每个整数都在范围 [0, 2n2^n2n - 1] 内(含 0 和 2n−12^n - 12n−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
Typescript 版算法实现
1 )方案 1:递归
function grayCode(n: number): number[] {
// 递归函数,用来算输入为n的格雷编码序列
const recursionFn = (n: number) => {
if (n === 1) return ['0', '1']
const prev: string[] = recursionFn(n - 1)
const result: string[] = []
const max: number = Math.pow(2, n) - 1 // 最大值
// 生成新的数组
for (let i = 0, len = prev.length; i < len; i++) {
result[i] = `0${prev[i]}` // 生成前i个,前一半
result[max - i] = `1${prev[i]}` // 生成后一半,和上面是对称的(倒序)
}
return result
}
return recursionFn(n).map(item => parseInt(item, 2)) // 这里二进制转十进制
}
- 这里是基于演草纸上画出得到一个规律
- 当n为1时
- 二进制输出:[0,1],换成十进制输出是:[0,1]
- 0 - 0
- 1 - 1
- 这里还看不出什么
- 当n为2时
- 二进制输出:[0,1,11,10],换成十进制就是 [0,1,3,2] 竖着看如下
- 00 - 0
- 01 - 1
- 11 - 3
- 10 - 2
- 从上面看出,最终是偶数个总数,中间劈开一半各自对称
- 比如:00 对应 11;01对应10
- 当n为3时
- 输出:[0,1,11,10,110,111,101,100],换成十进制是:[0,1,3,2,6,7,5,4],竖着排列
- 000 - 0
- 001 - 1
- 011 - 3
- 010 - 2
- 110 - 6
- 111 - 7
- 101 - 5
- 100 - 4
- 从上述可以看到验证了上述规律
- 这里输入的n和n-1有关,直到和最后的1有关,而1就是 [‘0’, ‘1’]的字符串
- 这种方案属于归纳总结产生的,因为用到的是递归,容易爆栈,效率还不高
2 )方案 2:基于迭代的算法策略
function grayCode(n: number): number[] {
// 初始化n为1位时的场景
const result = [0, 1]
// 对n位进行遍历
for (let i = 1; i < n; i++) {
const len = result.length
// 倒序循环,内层循环一次完毕,会递增len的长度,也就是让result中的元素翻一倍
for (let j = len - 1; j >= 0; j--) {
// 进行左移操作,并加入当前的值
result.push((1 << i) + result[j])
}
}
return result
};
- 这是官方示例,两个for循环,内部使用了左移操作
- 这个算法很精妙,比方案1好很多,而且效率也较高
- 这种算法策略利用了格雷编码的特性
- 相邻两个格雷码之间只有一位二进制位不同
- 通过对当前已有的格雷编码序列进行左移操作
- 并在前面加上新的二进制位(0 或 1), 可以得到新的格雷编码序列
- 而通过在内层循环中倒序遍历已有的格雷编码序列
- 并在每个元素前加上左移操作得到的新值
- 可以保证生成的新格雷编码序列满足相邻两个格雷码之间只有一位不同的条件
3 )方案3:利用已有的格雷编码来生成新的格雷编码
function grayCode(n: number): number[] {
// 初始化结果数组,初始值为 0
const ret: number[] = [0];
// 遍历生成长度为 n 的格雷编码序列
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 ret; // 返回生成的格雷编码序列数组
};
- 初始化结果数组:首先,创建一个数组 ret,并将初始值 0 放入其中。这是因为格雷编码序列的起始值总是 0
- 遍历生成格雷编码序列:使用一个循环来生成长度为 n 的格雷编码序列。从 1 开始遍历,直到 n
- 遍历已有的格雷编码:在每次迭代中,通过遍历已有的格雷编码来生成新的格雷编码
- 从已有的格雷编码数组的最后一个元素开始,向前遍历直到第一个元素
- 位运算生成新的格雷编码:在内部循环中,对已有的格雷编码进行位运算来生成新的格雷编码
- 具体来说,通过按位或运算将 1 左移 (i - 1) 位后,与已有的格雷编码进行按位或操作,生成新的格雷编码
- 将新的格雷编码添加到结果数组:将生成的新格雷编码添加到结果数组中
- 返回结果数组:最后,返回生成的格雷编码序列数组 ret
4 )方案4:位运算策略来生成格雷编码序列
function grayCode(n: number): number[] {
// 初始化结果数组
const ret: number[] = [];
// 遍历生成格雷编码序列
for (let i = 0; i < 1 << n; i++) {
// 根据格雷编码生成规则,将当前数值添加到结果数组中
ret.push((i >> 1) ^ i);
}
return ret; // 返回生成的格雷编码序列数组
}
-
遍历所有可能的格雷编码
- 通过循环遍历
0到2^n - 1的所有数值,其中n是格雷编码序列的长度 - 这保证了所有可能的格雷编码都被生成
- 通过循环遍历
-
位运算生成格雷编码:对于每个数值
i,通过位运算生成相应的格雷编码grayCode(i) = (i >> 1) ^ i- 这里
>>是右移位操作,^是异或操作 - 这个公式的实现效果是将当前数值右移一位,然后与原始数值进行异或操作,从而生成对应的格雷编码
- 这里
-
添加到结果数组:将生成的格雷编码依次添加到结果数组中
Python3 版算法实现
1 )方案 1:递归
import math
class Solution:
def grayCode(self, n: int) -> List[int]:
# 递归函数,用来算输入为n的格雷编码序列
def recursionFn(n):
if n == 1:
return ["0", "1"] # 基本情况:n=1时,返回初始的格雷编码序列
# 递归调用,获取前一级别的格雷编码序列
prev = recursionFn(n - 1)
# 结果数组长度为前一级别格雷编码序列长度的两倍
result = [None] * (2 * len(prev))
# 最大值,即格雷编码的最后一个值
max_val = int(math.pow(2, n)) - 1
# 遍历前一级别格雷编码序列
for i in range(len(prev)):
# 生成前一半的格雷编码序列,每个值在前面加上 0
result[i] = "0" + prev[i]
# 生成后一半的格雷编码序列,每个值在前面加上 1,且位置与前一半对称
result[max_val - i] = "1" + prev[i]
return result
# 调用递归函数计算格雷编码序列
result = recursionFn(n)
# 返回最终结果数组
return [int(item, 2) for item in result]
2 )方案 2:基于迭代的算法策略
class Solution:
def grayCode(self, n: int) -> List[int]:
# 初始化n为1位时的场景
result = [0, 1]
# 对n位进行遍历
for i in range(1, n):
length = len(result)
# 倒序循环,内层循环一次完毕,会递增length的长度,也就是让result中的元素翻一倍
for j in range(length - 1, -1, -1):
# 进行左移操作,并加入当前的值
result.append((1 << i) + result[j])
return result
3 )方案 3:利用已有的格雷编码来生成新的格雷编码
class Solution:
def grayCode(self, n: int) -> List[int]:
# 初始化结果数组,初始值为 0
ret = [0]
# 遍历生成长度为 n 的格雷编码序列
for i in range(1, n + 1):
# 获取当前结果数组的长度
m = len(ret)
# 从后往前遍历已有的格雷编码序列
for j in range(m - 1, -1, -1):
# 在已有的格雷编码基础上进行位运算,生成新的格雷编码,并添加到结果数组中
ret.append(ret[j] | (1 << (i - 1)))
return ret # 返回生成的格雷编码序列数组
4 )方案 4:位运算策略来生成格雷编码序列
class Solution:
def grayCode(self, n: int) -> List[int]:
# 初始化结果数组
ret = []
# 遍历生成格雷编码序列
for i in range(1 << n):
# 根据格雷编码生成规则,将当前数值添加到结果数组中
ret.append((i >> 1) ^ i)
return ret # 返回生成的格雷编码序列数组
Golang 版算法实现
1 )方案 1:递归
import "math"
func grayCode(n int) []int {
// 递归函数,用来算输入为n的格雷编码序列
var recursionFn func(int) []string
// 定义递归函数
recursionFn = func(n int) []string {
if n == 1 {
return []string{"0", "1"} // 基本情况:n=1时,返回初始的格雷编码序列
}
// 递归调用,获取前一级别的格雷编码序列
prev := recursionFn(n - 1)
// 结果数组长度为前一级别格雷编码序列长度的两倍
result := make([]string, 2*len(prev))
// 最大值,即格雷编码的最后一个值
max := int(math.Pow(2, float64(n))) - 1
// 遍历前一级别格雷编码序列
for i := 0; i < len(prev); i++ {
// 生成前一半的格雷编码序列,每个值在前面加上 0
result[i] = "0" + prev[i]
// 生成后一半的格雷编码序列,每个值在前面加上 1,且位置与前一半对称
result[max-i] = "1" + prev[i]
}
return result
}
// 调用递归函数计算格雷编码序列
result := recursionFn(n)
// 初始化最终结果数组
finalResult := make([]int, len(result))
// 遍历计算得到的格雷编码序列
for i, item := range result {
// 初始化十进制值
val := 0
// 遍历格雷编码的每一位
for _, ch := range item {
// 根据格雷编码规则计算对应的十进制值
val = val*2 + int(ch-'0')
}
// 将计算得到的十进制值存入最终结果数组
finalResult[i] = val
}
// 返回最终结果数组
return finalResult
}
2 )方案 2:基于迭代的算法策略
func grayCode(n int) []int {
// 初始化n为1位时的场景
result := []int{0, 1}
// 对n位进行遍历
for i := 1; i < n; i++ {
length := len(result)
// 倒序循环,内层循环一次完毕,会递增length的长度,也就是让result中的元素翻一倍
for j := length - 1; j >= 0; j-- {
// 进行左移操作,并加入当前的值
result = append(result, (1<<i)+result[j])
}
}
return result
}
3 )方案 3:利用已有的格雷编码来生成新的格雷编码
func grayCode(n int) []int {
// 初始化结果数组,初始值为 0
ret := []int{0}
// 遍历生成长度为 n 的格雷编码序列
for i := 1; i <= n; i++ {
// 获取当前结果数组的长度
m := len(ret)
// 从后往前遍历已有的格雷编码序列
for j := m - 1; j >= 0; j-- {
// 在已有的格雷编码基础上进行位运算,生成新的格雷编码,并添加到结果数组中
ret = append(ret, ret[j]|(1<<(i-1)))
}
}
return ret // 返回生成的格雷编码序列数组
}
4 )方案 4:位运算策略来生成格雷编码序列
func grayCode(n int) []int {
// 初始化结果数组
ret := make([]int, 0)
// 遍历生成格雷编码序列
for i := 0; i < 1<<uint(n); i++ {
// 根据格雷编码生成规则,将当前数值添加到结果数组中
ret = append(ret, i>>1^i)
}
return ret // 返回生成的格雷编码序列数组
}
404



被折叠的 条评论
为什么被折叠?



