力扣刷題——3007.價值和小於等於 K 的最大數字

SuzumiyaYui發表於2024-09-01

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

相關文章