【每日算法Day 83】鄰居小孩一年級就會的乘法表,你會嗎?

題目鏈接

LeetCode 668. 乘法表中第k小的數[1]

題目描述

幾乎每一個人都用乘法表。但是你能在乘法表中快速找到第 k 小的數字嗎?

給定高度 m、寬度 n 的一張 m \times n 的乘法表,以及正整數 k,你需要返回表中第 k 小的數字。

示例1

        輸入:
m = 3, n = 3, k = 5
輸出:
3
解釋:
乘法表:
1	2	3
2	4	6
3	6	9
第5小的數字是 3 (1, 2, 2, 3, 3).

      

示例2

        輸入:
m = 2, n = 3, k = 6
輸出:
6
解釋:
乘法表:
1	2	3
2	4	6
第6小的數字是 6 (1, 2, 2, 3, 4, 6).

      

說明:

  • mn 的範圍在 [1, 30000] 之間。
  • k 的範圍在 [1, mn] 之間。

題解

二分法

因爲 mn 數量級是 9 \times 10^8 級別的,所以顯然不能直接枚舉,要想一個對數級別的算法。

對數級別首先想到的肯定是二分了,我們二分第 k 小的數 mid ,然後求出乘法表中小於等於 mid 的數的數量 cnt 。如果發現 cnt \le mid ,那就說明這個答案太大了,還可以繼續縮小。否則的話答案太小了,得增大一點。

那麼對於枚舉的答案 mid 來說,如何找到乘法表中有多少小於等於它的數呢?我們可以直接從 1 開始枚舉,和 1 相乘並且結果小於等於 mid 的數有 mid 個,當然還有個 n 的限制,所以是 \min{(mid, n)} 個。然後和 2 相乘並且結果小於等於 mid 的數有 \min{(\left\lfloor\frac{mid}{2}\right\rfloor, n)} 個。依此類推下去,最終和 m 相乘並且結果小於等於 mid 的數有 \min{(\left\lfloor\frac{mid}{m}\right\rfloor, n)} 個。

所以最終小於等於 mid 的個數 cnt 就可以計算爲:

\sum_{i=1}^{m}{\min{\left(\left\lfloor\frac{mid}{i}\right\rfloor, n\right)}} \\

二分法+優化

當然這題計算還可以進行一些優化。

首先第 k 小的數是一定小於等於 k 的,所以我們的二分上界可以定爲 k

其次注意到當 i > mid 之後,個數一定是 0,所以 i 只需要枚舉到 \min{(mid, m)} 就行了。

然後當 i \le \left\lfloor\frac{mid}{n}\right\rfloor 時,有 \min{\left(\left\lfloor\frac{mid}{i}\right\rfloor, n\right)} = n,所以這部分的求和結果就是 n\left\lfloor\frac{mid}{n}\right\rfloor 。所以 cnt 又可以寫爲:

n\left\lfloor\frac{mid}{n}\right\rfloor + \sum_{i=\left\lfloor\frac{mid}{n}\right\rfloor+1}^{\min{(mid, m)}}{\left\lfloor\frac{mid}{i}\right\rfloor} \\

最後,對於某個 i = t ,我們會發現如果 i 慢慢增大,某一段連續區間內 \left\lfloor\frac{mid}{i}\right\rfloor 的值都是不會變的。而 i 最大可以增大到 \left\lfloor\frac{mid}{\left\lfloor\frac{mid}{t}\right\rfloor}\right\rfloor,那麼這一段區間內的求和就可以直接算出來:

\left\lfloor\frac{mid}{t}\right\rfloor \left(\left\lfloor\frac{mid}{\left\lfloor\frac{mid}{t}\right\rfloor}\right\rfloor-t+1\right) \\

接着令 i 直接跳轉到 \left\lfloor\frac{mid}{\left\lfloor\frac{mid}{t}\right\rfloor}\right\rfloor + 1 就可以了,這樣就不用慢慢加 1 計算了。要特別注意的是最後不能超過 m

理論上這樣的計算複雜度是更低的,但是實際運行中速度還不如不加最後一步優化,可能原因是除法操作次數太多了,反而總的操作次數超過了直接遍歷計算。

代碼

二分法(c++)

        class Solution {
public:
    int findKthNumber(int m, int n, int k) {
        int l = 1, r = m*n;
        while (l < r) {
            int mid = l+((r-l)>>1);
            if (enough(mid, m, n, k)) r = mid;
            else l = mid+1;
        }
        return l;
    }

    bool enough(int x, int m, int n, int k) {
        int cnt = 0;
        for (int i = 1; i <= m; ++i) {
            cnt += x/i<n?x/i:n;
        }
        return cnt >= k;
    }
};

      

二分法+優化(c++)

        class Solution {
public:
    int findKthNumber(int m, int n, int k) {
        int l = 1, r = k;
        while (l < r) {
            int mid = l+((r-l)>>1);
            if (enough(mid, m<mid?m:mid, n<mid?n:mid, k)) r = mid;
            else l = mid+1;
        }
        return l;
    }

    bool enough(int x, int m, int n, int k) {
        int cnt = n*(x/n), d = 0;
        for (int i = (x/n)+1; i <= m; i = d+1) {
            d = x/(x/i);
            cnt += (x/i)*((d<m?d:m)-i+1);
        }
        return cnt >= k;
    }
};

      

二分法(python)

        class Solution:
    def findKthNumber(self, m: int, n: int, k: int) -> int:
        def enough(x, m, n, k):
            cnt = 0
            for i in range(1, m+1):
                cnt += x//i if x//i<n else n
            return cnt >= k

        l, r = 1, m*n
        while l < r:
            mid = l+((r-l)>>1)
            if enough(mid, m, n, k): r = mid
            else: l = mid+1
        return l

      

二分法+優化(python)

        class Solution:
    def findKthNumber(self, m: int, n: int, k: int) -> int:
        def enough(x, m, n, k):
            cnt, i, d = n*(x//n), x//n+1, 0
            while i <= m:
                d = x//(x//i)
                cnt += (x//i)*((d if d<m else m)-i+1)
                i = d+1
            return cnt >= k

        l, r = 1, k
        while l < r:
            mid = l+((r-l)>>1)
            if enough(mid, m if m<mid else mid, n if n<mid else mid, k): r = mid
            else: l = mid+1
        return l

      

參考資料

[1]

LeetCode 668. 乘法表中第k小的數: leetcode-cn.com/problem

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章