题目描述
给你两个正整数 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 长度范围以内。
例如输入:n = 3, k = 1,会输出:"0"。因为S3 为 "0111001",其第 1 位为 "0" 。
输入格式
第一行2个正整数:N K。N 范围[2, 30];
输出格式
1或0
输入/输出例子1
输入:
4 11
输出:
1
解题思路
这题的思路非常巧妙,通过观察我们知道二进制字符串Sn中间位都是1,左右两边是取反后并对称。所以,如果某一位处在后半段,我们可以找到前半段对应数位取反即可求到答案。这样我们可以把问题规模变小,当字符串长度为1或者是要求的数位处于S的中间位时我们就可以得到答案,然后结束。
因此我们可以定义递归函数,有三个组成要素,一个是字符串长度l,一个是我们要求的位k,还有一个是是否进行了取反操作rv,rv=1表示做了取反,rv=0表示没有取反。对于两个结束条件的返回值,先讨论通过若干次递归,问题规模变小,使得l=1的情况,对于这种情况如果没有取反操作的,我们观察S1可知,返回0(此时rv也是为0)。如果是后半段往前找的,就要取反,函数传的参数rv=1,返回1。归纳起来就是,如果长度为1时,返回rv。再看要求的数位处于S的中间位的情况,如果k在原字符串中本来就是处于前半段的,且折半后k在中间位就返回1。如果k在原字符串中本来就是处于后半段的,将其对应到前半段的位置l-k+1,且折半后在中间位就返回0(此时要取反)。所以返回的应该是1-rv。如果没达到结束条件,就进行折半和比较,直到到达结束条件为止。
AC代码
cpp
#include<bits/stdc++.h>
using namespace std;
int n,k;
int dg(int l,int k,int rv){
if(l==1) return rv;
int m=(l+1)/2;
if(m==k) return 1-rv;
if(m<k) dg(l/2,l-k+1,1-rv);
else dg(l/2,k,rv);
}
int main(){
cin>>n>>k;
int ln=0;
for(int i=1;i<=n;i++) ln=ln*2+1;
cout<<dg(ln,k,0);
return 0;
}