根据题意,不难想到该题的暴力解法,从数字1
开始,逐个累加。每次检查由当前数字num
所构成的累加价值是否大于k
,假如为真,那么可以输出上一个数字,即num-1
class Solution {
public:
long long findMaximumNumber(long long k, int x) {
long long subSum = 0;
for(long long num = 1; ; num++)
{
int tem = num;
while(tem)
{
tem = tem >> (x - 1);
subSum += tem & 1;
tem = tem >> 1;
}
if(subSum > k)
return num - 1;
}
}
};
但是会超时,重新思考问题,由于本质上是一个查找问题,并且在暴力搜索的过程中,累加价值一直在增加。因此可知累加价值是一个单调增加的数列,可以通过二分查找的方式减少搜索的次数。若使用二分查找,需要考虑两个问题:第一,二分的初始左右边界如何界定;第二,如何快速的求得当前所查找数值的累加价值。
对于第一个问题,左边界比较好确定,应当为1。设定右边界可以这样思考:假定一个极限情况,k
的价值为0,那么num = (k + 1) << x
时,num
的价值为1,在这种情况下,num
的第x
位为1(num
的价值至少为1)的数字组合一共有((k + 1) << x) - (1 << x) + 1
种,那么该数值的累加价值一定大于k
。
对于第二个问题,观察下表,之后经过归纳可以发现,一个数值的价值有周期性规律,当x = 2
时,第一种周期为4,第一种周期的前半周期提供的价值为0,而后半周期提供的价值为1;第二种周期则为16。可以计算一个数字满足几个周期,然后再分别计算每个周期分别能提供多少价值,将它们累加起来就得到了累加价值。
以x=2
,num=14
为例子,第一种周期,每4个数提供两个价值;第二种周期,每16个数提供8个价值:
首先计算第一种周期能够提供多少价值,对于完整的提供一个周期的价值,计算公式为((14 + 1) / pow(2,2)) * pow(2,1) = 6
,乘号左边代表数字中存在多少个完整的第一周期,右边则代表每满足一个第一种周期会提供多少价值。此外,注意到14
在不完整的第一种周期内,有部分在第一周期的下半周期内(12,13
位于上半周期,14
则位于下半周期),因此还需把这部分提供的价值加上,计算公式为((14 + 1) % pow(2,2)) - pow(2,1) = 1
。相加后,得到了第一周期提供给14的累加价值7
。
因为14
不存在完整的第二种周期,所以14
从第二种周期的部分下半周期获得价值,计算公式为((14 + 1) % pow(2,4)) - pow(2,3) = 7
,则第二种周期提供给14
的累加价值为7
。
x | num | 二进制表示 | 价值 | 累加价值 |
---|---|---|---|---|
2 | 0 | 0000 | 0 | 0 |
2 | 1 | 0001 | 0 | 0 |
2 | 2 | 0010 | 1 | 1 |
2 | 3 | 0011 | 1 | 2 |
2 | 4 | 0100 | 0 | 2 |
2 | 5 | 0101 | 0 | 2 |
2 | 6 | 0110 | 1 | 3 |
2 | 7 | 0111 | 1 | 4 |
2 | 8 | 1000 | 1 | 5 |
2 | 9 | 1001 | 1 | 6 |
2 | 10 | 1010 | 2 | 8 |
2 | 11 | 1011 | 2 | 10 |
2 | 12 | 1100 | 1 | 11 |
2 | 13 | 1101 | 1 | 12 |
2 | 14 | 1110 | 2 | 14 |
2 | 15 | 1111 | 2 | 16 |
实现代码如下:
long long getPeriodPrice(long long num, int x)
{
long long period = 1LL << x;
//计算完整的周期提供的价值
long long res = (num + 1) / period * pow(2, x - 1);
//计算不完整的周期提供的价值
if((num + 1) % period >= (period >> 1))
res += (num + 1) % period - (period >> 1);
return res;
}
long long getAccuPrice(long long num, int x)
{
int bitNum = 0;
long long tem = num;
while(tem)
{
tem = tem >> 1;
bitNum++;
}
long long res = 0;
for(int i = x; i <= bitNum; i += x)
{
res += getPeriodPrice(num, i);
}
return res;
}
long long findMaximumNumber(long long k, int x) {
long long left = 1, right = (k + 1) << x;
while(left < right)
{
long long mid = (left + right + 1) / 2;
long long a = getAccuPrice(mid, x);
if(a > k)
{
right = mid - 1;
}
else
{
left = mid;
}
}
return left;
}
标签:tem,周期,long,力扣,3007,num,14,价值,刷题
From: https://www.cnblogs.com/yuhixyui/p/18383694