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=21−1
第二行个数为 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∗(21−1)=22−1=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∗(22−1)=23−1=7,
。。。
于是可推导出每一行的数字的个数
t
o
t
a
l
{total}
total与行数
n
{n}
n的关系:
t
o
t
a
l
=
2
n
−
1
{total = 2^n-1}
total=2n−1。
得到每一行个数有什么用呢?当然有用,而且很有用。
仔细观察题目的要求——
-
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} 23−1=7个数,6>4, 所以我们要找的数在右边,那么与他互补的数就是第3行第 2 3 − 6 = 2 {2^3-6 = 2} 23−6=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异或等于互补的数