力扣刷题——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=2num=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;
    }
posted @ 2024-09-01 10:18  SuzumiyaYui  阅读(29)  评论(0)    收藏  举报