一道经典递归题,两种做法,常规递归做法和模拟数学规律解法
3695. 扩充序列 - AcWing题库
扩充序列
样例解释
对于样例 1,经过 2 次扩充,得到序列 [1,2,1,3,1,2,1]其第 2 个元素为 2。
对于样例 2,经过 3次扩充,得到序列 [1,2,1,3,1,2,1,4,1,2,1,3,1,2,1]其第 8个元素为 4。
思路一
先看序列的长度。N=1时,序列长度为1,N=2时,序列长度为3
N=3时,序列长度为7,N=4时,序列长度为15
不难看出规律
假设长度为Len,则N和长度的关系为
Len=pow(2,n)-1;
则我们在算K时,有三种情况
情况1
是k=pow(2,n-1),则K是最中间那个数,也就是扩容第N-1次时,新增的那个数,通过样例可以看出,新增的数,就是N本身,例如扩容3次,新增的数是4
1,2 ,1,3, 1,2, 1,4, 1,2, 1,3 ,1,2 ,1
所以直接返回N,即是结果,k的值
情况2
是k<pow(2,n-1),接着计算n=n-1时,k的值是多少
情况3
是k>pow(2,n-1),只需要把k-(pow(2,n-1)),然后按情况2计算即可,因为中点左右两边的序列,是一样的
通过这三种情况,发现可以直接递归做,看一下数据范围
n<50,最多是O(4n),时间复杂度没问题,但是pow(2,50)爆int,需要longlong
实现代码1
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll ;
ll n,k,res;
ll dfs(ll k){if(k==pow(2,n-1))return n;//情况一,确定是新增数,直接返回//如果是情况三,则变成情况二if(k>pow(2,n-1))k-=pow(2,n-1);//缩小范围,只需要查找在上次扩充之前出现的数n-=1;//递归dfs(k);
}
int main(){cin>>n>>k;res=dfs(k);cout<<res;return 0;
}
思路二
发现数列规律
所有奇数位都是1,也就是如果k%2!=0,则k的值为1,这里我们只看值,则k是最初始的数
反之
如果k%2==0,则k一定是扩充的数,那么我们利用k,就可以推断出,k位对应的值,是在第几次扩充出现的
例如
1,2 ,1,3, 1,2, 1,4, 1,2, 1,3 ,1,2 ,1
红4是第八位,对应的k是8,k/2=4,4/2=2,2/2=1,k能除以3次,所以k对应的值,是第三次扩充后出现
1,2 ,1,3, 1,2, 1,4, 1,2, 1,3 ,1,2 ,1
红2是第十位,k=10,10/2=5,5是奇数,所以不用除了,k除了一次,k对应的值,是在第一次扩充时出现的
1,2 ,1,3, 1,2, 1,4, 1,2, 1,3 ,1,2 ,1
红3是第十二位,k=12,12/2=6,6/2=3,3是奇数,不用除了,k除了一次,k对应的值,是在第二次扩充时出现的
第三次扩充出现的值是4,第二次扩充出现的值是3,第一次扩充出现的值是2
所以我们只需要计算,k对应的值是第几次扩充时出现的,然后+1即可
实现代码二
#include<iostream>
using namespace std;
typedef long long ll ;ll n,k,res=0;
int main(){cin>>n>>k;//k&1是位运算,如果&1为真,则k%2!=0while(!(k&1))k>>=1,res++;cout<<res+1;return 0;
}