力扣刷题——3007.价值和小于等于 K 的最大数字
根据题意,不难想到该题的暴力解法,从数字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;
    }

                
            
        
浙公网安备 33010602011771号