【779. 第K个语法符号】

来源:力扣(LeetCode)

描述:

  我们构建了一个包含 n 行( 索引从 1 开始 )的表。首先在第一行我们写上一个 0。接下来的每一行,将前一行中的0替换为01,1替换为10。

  • 例如,对于 n = 3 ,第 1 行是 0 ,第 2 行是 01 ,第3行是 0110

  给定行数 n 和序数 k,返回第 n 行中第 k 个字符。( k 从索引 1 开始

示例 1:

输入: n = 1, k = 1
输出: 0
解释: 第一行:0

示例 2:

输入: n = 2, k = 1
输出: 0
解释: 
第一行: 0 
第二行: 01

示例 3:

输入: n = 2, k = 2
输出: 1
解释:
第一行: 0
第二行: 01

提示:

  • 1 <= n <= 30

  • 1 <= k <= 2n-1

方法一:递归

思路与算法

  首先题目给出一个 n 行的表(索引从 1 开始)。并给出表的构造规则为:第一行仅有一个 0,然后接下来的每一行可以由上一行中 0 替换为 01,1 替换为 10 来生成。

  • 比如当 n = 3 时,第 1 行是 0,第 2 行是 01,第 3 行是 0110。

  现在要求表第 n 行中第 k 个数字,1 ≤ k ≤ 2n 。首先我们可以看到第 i 行中会有 2i−1 个数字,1 ≤ i ≤ n,且其中第 j 个数字按照构造规则会生第 i + 1 行中的第 2 * j - 1 和 2 * j 个数字, 1 ≤ j ≤ 2i−1。即对于第 i + 1 行中的第 x 个数字 num1 , 1 ≤ x ≤ 2i,会被第 i 行中第 (x+1) / 2 个数字 num2 生成。且满足规则:

  • 当 num2 = 0 时,num2 会生成 01:
    1

  • 当 num2 = 1 时,num2 会生成 10:
    2

  并且进一步总结我们可以得到:num1 = (x & 1) ⊕ 1 ⊕ num2 ,其中 & 为「与」运算符,⊕ 为「异或」运算符。那么我们从第 n 不断往上递归求解,并且当在第一行时只有一个数字,直接返回 0 即可。

代码:

class Solution {
public:
    int kthGrammar(int n, int k) {
        if (n == 1) {
            return 0;
        }
        return (k & 1) ^ 1 ^ kthGrammar(n - 1, (k + 1) / 2);
    }
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.9 MB, 在所有 C++ 提交中击败了23.30%的用户
复杂度分析
时间复杂度:O(n),其中 n 为题目给定表的行数,递归深度为 n。
空间复杂度:O(n),其中 n 为题目给定表的行数,主要为递归的空间开销。

方法二:找规律 + 递归

思路与算法

按照方法一,我们可以尝试写表中的前几行:

  • 0
  • 01
  • 0110
  • 01101001

  我们可以注意到规律:每一行的后半部分正好为前半部分的“翻转”——前半部分是 0 后半部分变为 1,前半部分是 1,后半部分变为 0。且每一行的前半部分和上一行相同。我们可以通过「数学归纳法」来进行证明。

  有了这个性质,那么我们再次思考原问题:对于查询某一个行第 k 个数字,如果 kk 在后半部分,那么原问题就可以转化为求解该行前半部分的对应位置的“翻转”数字,又因为该行前半部分与上一行相同,所以又转化为上一行对应对应的“翻转”数字。那么按照这样一直递归下去,并在第一行时返回数字 0 即可。

代码:

class Solution {
public:
    int kthGrammar(int n, int k) {
        if (k == 1) {
            return 0;
        }
        if (k > (1 << (n - 2))) {
            return 1 ^ kthGrammar(n - 1, k - (1 << (n - 2)));
        }
        return kthGrammar(n - 1, k);
    }
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.7 MB, 在所有 C++ 提交中击败了89.25%的用户
复杂度分析
时间复杂度:O(n),其中 n 为题目给定表的行数。
空间复杂度:O(n),其中 n 为题目给定表的行数,主要为递归的空间开销。

方法三:找规律 + 位运算

思路与算法

  在「方法二」的基础上,我们来进行优化,本质上我们其实只需要求在过程中的“翻转”总次数,如果“翻转”为偶数次则原问题求解为 0,否则为 1。

  首先我们修改行的索引从 0 开始,此时原先第 p 行的索引现在为 p - 1 行,第 i 行有 2i 位。那么对于某一行 i 中下标为 x 的数字,如果 x < 2i−1 那么等价于求 i - 1 中下标为 x 的数字,否则 x 的二进制位的从右往左第 i + 1 位为 1,此时需要减去该位(“翻转”一次),然后递归求解即可。所以我们可以看到最后“翻转”的总次数只和初始状态下的下标 x 二进制表示中 1 的个数有关。因此原问题中求“翻转”的总次数就等价于求 k - 1 的二进制表示中 1 的个数。

代码:

class Solution {
public:
    int kthGrammar(int n, int k) {
        // return __builtin_popcount(k - 1) & 1;
        k--;
        int res = 0;
        while (k > 0) {
            k &= k - 1;
            res ^= 1;
        }
        return res;
    }
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.7 MB, 在所有 C++ 提交中击败了85.66%的用户
复杂度分析
时间复杂度:O(logk),其中 k 为题目给定查询的下标。
空间复杂度:O(1),仅使用常量变量。
author:LeetCode-Solution

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值