蓝桥杯冲刺训练营之递归——例4.找出第 N 个二进制字符串中的第 K 位

1. 题目

找出第 N 个二进制字符串中的第 K 位。

给你两个正整数 n 和 k,二进制字符串 Sn 的形成规则如下:

  • S1 = "0"

  • 当 i > 1 时,Si = Si-1 + “1” + reverse(invert(Si-1))

其中 + 表示串联操作,reverse(x) 返回反转 x 后得到的字符串,而 invert(x) 则会翻转 x 中的每一位(0 变为 1,而 1 变为 0)。

例如,符合上述描述的序列的前 4 个字符串依次是:

  • S1 = “0”
  • S2 = “011”
  • S3 = “0111001”
  • S4 = “011100110110001”

请你返回 Sn 的 第 k 位字符 ,题目数据保证 k 一定在 Sn 长度范围以内。

示例 1:
输入:n = 3, k = 1
输出:“0”
解释:S3 为 “0111001”,其第 1 位为 “0” 。

示例 2:
输入:n = 4, k = 11
输出:“1”
解释:S4 为 “011100110110001”,其第 11 位为 “1” 。

示例 3:
输入:n = 1, k = 1
输出:“0”

示例 4:
输入:n = 2, k = 3
输出:“1”

2. 解题思路

这题和我上一篇文章(第K个语法符号)讲解的题很类似,可以参考着一起看。

同样的,解决这种递归类型的题,需要找到递归问题的三要素:子问题、变量、边界条件。那么接下来就通过一步步分析得出这三要素。

跟据提意可知,下一行等于上一行个数的两倍加1

第一行个数为 1 = 2 1 − 1 {1 = 2^1-1} 1=211

第二行个数为 2 ∗ ( 1 ) + 1 = 2 ∗ ( 2 1 − 1 ) = 2 2 − 1 = 3 {2*(1)+1 = 2*(2^1-1) = 2^2-1 = 3} 2(1)+1=2(211)=221=3

第三行个数为 2 ∗ ( 3 ) + 1 = 2 ∗ ( 2 2 − 1 ) = 2 3 − 1 = 7 {2*(3)+1 = 2*(2^2-1) = 2^3-1 = 7} 2(3)+1=2(221)=231=7,
。。。
于是可推导出每一行的数字的个数 t o t a l {total} total与行数 n {n} n的关系: t o t a l = 2 n − 1 {total = 2^n-1} total=2n1

得到每一行个数有什么用呢?当然有用,而且很有用。

仔细观察题目的要求——

  • S1 = "0"

  • 当 i > 1 时,Si = Si-1 + “1” + reverse(invert(Si-1))

是不是可以发现,每一行最中间那个数是定的:第一行最中间的数是0,其余行最中间的数是1。这不就是边界条件了吗?

现在找到了三要素之一,还有两个。那么子问题从哪分析得到呢?

仔细观察题目的这个条件:当 i > 1 时,Si = Si-1 + “1” + reverse(invert(Si-1))

这个条件告诉我们什么?每一行是关于中心互补的。下图可以很好的解释中心互补的意思:
中心互补
不难发现两个互补的位置他们相加为1。即,我知道一个数是0,那么和他互补的数就是1了。这一点很关键。

以最中间的数为分界线,左边数组成的字符串和右边数组成的字符串的反转互补。通过简单的规律寻找可以发现互补的两个数 a , b {a, b} a,b的位置和行数的关系: p o s a + p o s b = 2 n {pos_a + pos_b = 2^n} posa+posb=2n, p o s a {pos_a} posa表示数a在某一行的位置。

所以呢,我们想找右边的数,就可以通过找其在左边对应的互补的数再取补就可以了。

举个例子:

我们想找第3行第6个数:
由于第三行一共有 2 3 − 1 = 7 {2^3-1 = 7} 231=7个数,6>4, 所以我们要找的数在右边,那么与他互补的数就是第3行第 2 3 − 6 = 2 {2^3-6 = 2} 236=2个数。所以只要找到第3行第2个数,然后取补就是我们要找的第3行第6个数了。

既然右边的数可以通过左边的数取补找到,那么左边的数怎么找呢?还记得题目的条件吗:当 i > 1 时,Si = Si-1 + “1” + reverse(invert(Si-1)),发现了没?左边的数是不是就刚好是上一行的数呢?所以找左边的数是不是等于找上一行同样的数呢?这不就是子问题了吗?

子问题找到了,那么变量就很显然了,当然就是行数n和需要找的数k。(这好像是废话hhh)

再理一下思路:

  • 边界条件判断n是否等于1或者k是否是这行中间的数
  • 如果要找的数在中间的数右边,利用互补性质转换成求解左边的数
  • 左边的数等同于上一行的数,所以寻找左边的数等价于寻找上一行相同位置的数

3. 代码

class Solution {
public:

    char findKthBit(int n, int k) {
        
        if (n == 1) return '0';
        
        if (k == (1<<n)/2) return '1'; // 非第一层的中间的数均为1

        char ans;

        if (k > (1<<n)/2)
            ans = 1^((findKthBit(n, (1<<n)-k))-'0') + '0'; // 和1异或就是取反
        else 
            ans = findKthBit(n-1, k);

        return ans;
    }
};

注:

  • ( 1 < < n ) = 2 n {(1<<n) = 2^n} (1<<n)=2n,因为位运算快,所以采用。

  • 因为ans是char类型,所以char = 1时,char - ‘0’ = 1, 同理char = 0时,char - ‘0’ = 0

  • 和1异或等于互补的数

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zh4men9

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

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

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

打赏作者

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

抵扣说明:

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

余额充值