89. Gray Code(格雷编码)三种解法(C++ & 注释)

1. 题目描述

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。

给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。

格雷编码序列必须以 0 开头。

示例 1:

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

00 - 0
10 - 2
11 - 3
01 - 1

示例 2:

输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 20 = 1。因此,当 n = 0 时,其格雷编码序列为 [0]。

题目链接:中文题目英文题目

2. 回溯(Backtracking)

2.1 解题思路

首先这道题的最后一种解法(4. 位操作)简直就是神之技巧哇,叹为观止,建议大家务必看看最后一种解法,万一面试遇到相似题目,又要求用这种解法,如果没有事先了解过,现场临场发挥基本不太可能的说。好啦,不废话了,让我们先来讲解一下回溯法的思路吧。

先看看n = 3,情况一:

  1. 000
  2. 001
  3. 011
  4. 010
  5. 110
  6. 111
  7. 100
  8. 101

这种排列方法的递归顺序是:先找到最右边的数字,翻转之后,向后找到其最右边的数字在进行翻转;递归思路如下图所示:
在这里插入图片描述
情况二:

  1. 000
  2. 001
  3. 011
  4. 010
  5. 110
  6. 100
  7. 101
  8. 111

情况二的递归顺序是:先找到最右边的数字,翻转之后,向后从左往右的顺序进行翻转;递归思路如下图所示:
在这里插入图片描述
貌似两种情况都是OK的,但是当n = 4时,情况二走到1111的时候,会出现前后两个数字位数超过两个不一样的情况:

  1. 1111
  2. 1011
  3. 1001
  4. 1000
  5. 1101
  6. 1100

所以只能使用情况一的思路进行回溯。最后存储每位数可以使用string,但这里我们新介绍一个容器:bitset;这个容器只能存储1和0,或者True或False,函数原型为:template <size_t N> class bitset,所以可以使用 bitset Variable’s Name 来初始化长度为N的bitset;

Bitset相关成员函数介绍:
1)unsigned long to_ulong() const:将bitset转换成无符号长整型数字;
2)bitset& flip() 或 itset& flip (size_t pos):如果pos没有指定,则翻转容器中的所有值;如果pos指定,则翻转pos位置处的数字;注意bitset从右边开始从0排排序列,和一般的vector不一样;

2.2 实例代码

class Solution {
    vector<int> ans;
    bitset<32> bits;

    void utils(int idx) {
        if (!idx) { ans.push_back(bits.to_ulong()); return; }

        utils(idx - 1);
        bits.flip(idx - 1);
        utils(idx - 1);
    }

public:
    vector<int> grayCode(int n) {
        utils(n);
        return ans;
    }
};

3. 迭代(Iteration)

3.1 解题思路

还是n = 3,我们换个思路进行观察:

  1. 000
  2. 001
  3. 011
  4. 010
  5. 110
  6. 111
  7. 100
  8. 101

换一种排列方式:
在这里插入图片描述
我们发现右边的数只需把左边的数最低位变1就行,接着看看n = 2:
在这里插入图片描述
也有同样的规律,那么来看看n从2变化到3:
在这里插入图片描述
发现只需要在n = 2的基础上,某个数字末尾加0,加把最低位变1即可从n = 2转换到n = 3。现在从n = 0 ~ 3完整看一下整个思路的过程:
在这里插入图片描述
所以,我们只需要初始化一个答案数组(ans),包含一个空string;每轮循环末尾加0,然后最低位变一,即可得到最终答案。这部分对应代码:3.2.1 字符串转换

上面的思路是末尾变化,那么我们可不可以从头部开始变化呢?答案当然是No Problem啦,而且也无需使用string来表示数字,直接使用integer进行操作就行。因为正数的二进制前面都是0,所以也不需要我们进行补零操作,只需把最高位变1就行。比如n = 3, 000 -> 100,我们只需把1左移2位(001 << 2 = 100),然后再或运算(|)即可,移动的位数刚好等于外层循环的当前循环数:for (int i = 0; i < n; i++)。这部分对应代码:3.2.2 整数转换(推荐)

3.2 实例代码

3.2.1 字符串转换

class Solution {
public:
    vector<int> ans;

    // 将字符串二进制数转换成integer
    void convertoInt(string& nums) {
        int num = 0;
        for (int i = 0; i < nums.size(); i++) {
            num <<= 1;
            if (nums[i] == '1') num++;
        }
        //cout << num;
        ans.push_back(num);
    }

    vector<int> grayCode(int n) {
        if (!n) return vector<int>(1, 0);
        vector<string> nums(1, "");

        for (int i = 0; i < n; i++) {
            for (string& str : nums) str.push_back('0'); // 末尾加'0'

            // 末尾字符变为'1'
            for (int j = nums.size() - 1; j >= 0; j--) {
                nums.push_back(nums[j]);
                nums.back()[nums.back().size() - 1] = '1';
            }
        }

        for (string& str : nums) convertoInt(str);

        return ans;
    }
};

3.2.2 整数转换(推荐)

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> ans(1, 0);
        for (int i = 0; i < n; i++) {
            int len = ans.size();

            for (int j = len - 1; j >= 0; j--) ans.push_back(ans[j] | (1 << i));
        }

        return ans;
    }
};

4. 位操作(Bit Manipulation)

4.1 解题思路

经过两种方法的洗礼,终于来到了屠龙的时刻哒!最后一种方法只需两三行代码就能秒杀“巨龙”!

首先我们知道某个数加1,会把左边所有连续的1置零,然后把其相邻的0变1,比如0111 + 1 = 1000;为了方便理解,先假设这里二进制数最多10位,然后某个数的二进制表示为(X和?表示未知):
在这里插入图片描述
i ^ (i >> 1) 和 (i + 1) ^ ((i + 1) >> 1)蓝框部分异或后的结果都是相同的,只有0 ^ ? 和 1 ^ ?不同,而且无论?取0还是1,这部分都是不一样的。到这里我们得到了什么呢?说明两个连续的正数通过上述处理,即满足了格雷编码的要求:两个连续的数值仅有一个位数的差异。所以代码就会变得非常简单,遍历0 ~ n,再通过上述计算即可得到答案。

当然还有另一个情况,+1不会进位,这种情况和上面是基本类似的,不懂的同学可以参考下图,这里就不再赘述了:
在这里插入图片描述
这个方法是不是很震惊呀(ΩДΩ)~

4.2 实例代码

class Solution {
public:
    vector<int> grayCode(int n) {
        vector<int> ans(1 << n); // 1 << n 表示一共有多少个答案: 2 ^ n
        
        for (int i = 0; i < 1 << n; i++) ans[i] = i ^ (i >> 1);

        return ans;
    }
};

5. 参考资料

  1. Share my solution
  2. An accepted three line solution in JAVA
  3. Backtracking C++ solution
  4. 4 lines C++ code
  5. C++ bitset 用法
  6. std::bitset
  7. std::bitset::to_ulong
  8. std::bitset::flip

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值