LeetCode 779. 第K个语法符号【递归,找规律,位运算】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

我们构建了一个包含 n 行( 索引从 1  开始 )的表。首先在第一行我们写上一个 0。接下来的每一行,将前一行中的0替换为011替换为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 <= 2^n - 1

解法 递归

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

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

现在要求表第 n n n 行中第 k k k 个数字, 1 ≤ k ≤ 2 n 1 \le k \le 2 ^ n 1k2n 。首先我们可以看到第 i i i 行中会有 2 i − 1 2^{i-1} 2i1 个数字, 1 ≤ i ≤ n 1 \le i \le n 1in ,且其中第 j j j 个数字按照构造规则会生第 i + 1 i + 1 i+1 行中的第 2 ∗ j − 1 2*j - 1 2j1 2 ∗ j 2∗j 2j 个数字, 1 ≤ j ≤ 2 i − 1 1 \le j \le 2^{i-1} 1j2i1

即对于第 i + 1 i + 1 i+1 行中的第 x x x 个数字 num 1 \textit{num}_1 num1 1 ≤ x ≤ 2 i 1 \le x \le 2^i 1x2i ,会被第 i i i 行中第 ⌊ x + 1 2 ⌋ \lfloor \frac{x + 1}{2} \rfloor 2x+1 个数字 num 2 \textit{num}_2 num2 生成。且满足规则:

  • num 2 = 0 \textit{num}_2 = 0 num2=0 ​时, num 2 \textit{num}_2 num2 会生成 01 01 01
    num 1 = { 0 , x ≡ 1 ( m o d 2 ) 1 , x ≡ 0 ( m o d 2 ) \textit{num}_1 = \begin{cases} 0, & x \equiv 1 \pmod{2} \\ 1, & x \equiv 0 \pmod{2} \\ \end{cases} num1={ 0,1,x1(mod2)x0(mod2)
  • n u m 2 = 1 num_2 = 1 num2=1 时, num 2 \textit{num}_2 num2 会生成 10 10 10
    num 1 = { 1 , x ≡ 1 ( m o d 2 ) 0 , x ≡ 0 ( m o d 2 ) \textit{num}_1 = \begin{cases} 1, & x \equiv 1 \pmod{2} \\ 0, & x \equiv 0 \pmod{2} \\ \end{cases} num1={ 1,0,x1(mod2)x0(mod2)

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

扫描二维码关注公众号,回复: 17315726 查看本文章
class Solution {
    
    
public:
    int kthGrammar(int n, int k) {
    
    
        if (n == 1) return 0;
        return (k & 1) ^ 1 ^ kthGrammar(n - 1, (k + 1) / 2);
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 为题目给定表的行数,递归深度为 n n n
  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为题目给定表的行数,主要为递归的空间开销。

解法2 找规律 + 递归

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

  • 0 0 0
  • 01 01 01
  • 0110 0110 0110
  • 01101001 0110 1001 01101001
  • ⋯ \cdots

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

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

class Solution {
    
    
public:
    int kthGrammar(int n, int k) {
    
    
        if (k == 1) return 0;
        // 查询某一个行第k数,如果k在后半部分,可转化为求解该行前半部分对应位置的翻转数字
        if (k > (1 << (n - 2))) return 1 ^ kthGrammar(n - 1, k - (1 << (n - 2)));
        return kthGrammar(n - 1, k); // 一行前半部分和上一行相同
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 为题目给定表的行数。
  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 为题目给定表的行数,主要为递归的空间开销。

解法3 找规律 + 位运算

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

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

因此原问题中求“翻转”的总次数,就等价于求 k − 1 k - 1 k1 的二进制表示中 1 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;
    }
};

复杂度分析:

  • 时间复杂度: O ( log ⁡ k ) O(\log k) O(logk) ,其中 k k k 为题目给定查询的下标。
  • 空间复杂度: O ( 1 ) O(1) O(1) ,仅使用常量变量。

猜你喜欢

转载自blog.csdn.net/myRealization/article/details/134107460
今日推荐