【LeetCode每日一题】【递归/位运算】2022-10-20 779. 第K个语法符号 Java实现


题目链接

https://leetcode.cn/problems/k-th-symbol-in-grammar/

题目

在这里插入图片描述

题目大意

下一行是由之前一行进行扩展,上一行中的数字0,在下一行中扩展为01,上一行中的数字1,在一行中扩展为10。所以每一行的01序列都是在上一行的基础上扩展而来的,长度是前一行的两倍

我的思路

在这里插入图片描述

本来的想法是,按照题目的意思,把第n行的序列模拟出来,然后用charAt获取第k个位置上的元素,看到n和k的范围,觉得这种方法应该不可行

根据提示,可以使用递归

Try to represent the current (N, K) in terms of some (N-1, prevK). What is prevK ?

举个例子,比如n=5,k=15,也就是说,要求第5行的第15位上的数字是什么,借着上面的提示,可以想到:
第5行的第15个数字一定是第4行的第8个数字扩展来的
第4行的第8个数字一定是第3行的第4个数字扩展来的
第3行的第4个数字一定是第2行的第2个数字扩展来的
第2行的第2个数字一定是第1行的第1个数字扩展来的
第1行只有一个数字0,返回0
根据第1行的返回结果0,第2行的第2个数字(偶数位置),则是0扩展以后的第2位:1,返回1
根据第2行的返回结果1,第3行的第4个数字(偶数位置),则是1扩展以后的第2位:0,返回0
根据第3行的返回结果0,第4行的第8个数字(偶数位置),则是0扩展以后的第2位:1,返回1
根据第4行的返回结果1,第5行的第15个数字(奇数位置),则是1扩展以后的第1位:1,返回1
算法结束

class Solution {
    public int kthGrammar(int n, int k) {
        if (n == 1) {
            return 0;
        }
        int res;
        //当前k是奇数
        if ((k & 1) != 0) {
            res = kthGrammar(n - 1, (k + 1) / 2);
            if (res == 0) {
                return 0;
            } else {
                return 1;
            }
        } else {
            res = kthGrammar(n - 1, k / 2);
            if (res == 0) {
                return 1;
            } else {
                return 0;
            }
        }
    }
}

官方思路

方案一 递归

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

现要求表第n行中的第k个数字, 1 ≤ k ≤ 2 n − 1 1 \leq k \leq 2^{n-1} 1k2n1

首先,第i行中会有 2 i − 1 2^{i-1} 2i1个数字, 1 ≤ i ≤ n 1 \leq i \leq n 1in,且其中第j个数字按照构造规则会生成第i+1行中的第 2 ∗ j − 1 2 * j-1 2j1 2 ∗ j 2 * j 2j 个数字, 1 ≤ j ≤ 2 i − 1 1 \leq j \leq 2^{i-1} 1j2i1

即对于第 i + 1 i+1 i+1 行中的第 x x x 个数字 n u m 1 num_1 num1 1 ≤ x ≤ 2 i 1 \leq x \leq 2^{i} 1x2i ,会被第 i i i 行中第 ⌊ x + 1 2 ⌋ \lfloor \frac{x+1}{2} \rfloor 2x+1 个数字 n u m 2 num_2 num2 生成。且满足规则:

  • n u m 2 = 0 num_2 = 0 num2=0,会生成01
    • x为奇数时,下一行结果是0
    • x为偶数时,下一行结果是1

n u m 1 = { 0 , x ≡ 1 ( m o d    2 ) 1 , x ≡ 0 ( m o d    2 ) num_1 = \begin{cases} 0,x \equiv 1 (\mod 2) \\ 1,x \equiv 0 (\mod 2) \end{cases} num1={0x1(mod2)1x0(mod2)

  • n u m 2 = 1 num_2 = 1 num2=1,会生成10
    • x为奇数时,下一行结果是1
    • x为偶数时,下一行结果是0

n u m 1 = { 1 , x ≡ 1 ( m o d    2 ) 0 , x ≡ 0 ( m o d    2 ) num_1 = \begin{cases} 1,x \equiv 1 (\mod 2) \\ 0,x \equiv 0 (\mod 2) \end{cases} num1={1x1(mod2)0x0(mod2)

为啥我觉得他这个文字写的让人费解…

通俗点来说,就是,第i+1行的第x个数字num1,是由第i行的 第 ⌊ x + 1 2 ⌋ \lfloor \frac{x+1}{2} \rfloor 2x+1 个数字生成的num2生成的

所以,生成num1的两个因素:num2,以及 x 这个位置是奇数还是偶数

如果num2=0,并且,x是偶数,那么,0派生成01,num1=1;如果x是奇数,那么,num1=0。

如果num2=1,并且,x是偶数,那么,1派生成10,num1=0;如果x是奇数,那么,num1=1。

也就是说,如果x是奇数,那么num2是啥,num1就是啥;如果x是偶数,那么num2是1,num1就是0;num1是0,num2就是1

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
  • 01
  • 0110
  • 01101001
  • 0110100110010110

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

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

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

方案三 找规律 + 位运算(不太理解,欢迎讨论)

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

如果“翻转”为偶数次,那么原问题求解为0,否则为1。
什么意思

首先,修改行的索引从0开始,此时,原先第p行的索引现在为p-1行,第i行有 2 i 2^i 2i次方位。那么对于某一行i中下标x的数字,如果 x < 2 i − 1 x<2^{i-1} x<2i1,那么等价于求 i − 1 i-1 i1中下标为x的数字,否则,x的二进制位的从右往左第i+1位为1,此时需要减去该位(“翻转”一次),然后递归求解即可。

x的二进制位的从右往左第i+1位为1 看不太懂

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值