根據題意,不難想到該題的暴力解法,從數字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;
}