題目鏈接
LeetCode 668. 乘法表中第k小的數[1]
題目描述
幾乎每一個人都用乘法表。但是你能在乘法表中快速找到第 小的數字嗎?
給定高度 、寬度 的一張 的乘法表,以及正整數 ,你需要返回表中第 小的數字。
示例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).
說明:
- 和 的範圍在 之間。
- 的範圍在 之間。
題解
二分法
因爲 數量級是 級別的,所以顯然不能直接枚舉,要想一個對數級別的算法。
對數級別首先想到的肯定是二分了,我們二分第 小的數 ,然後求出乘法表中小於等於 的數的數量 。如果發現 ,那就說明這個答案太大了,還可以繼續縮小。否則的話答案太小了,得增大一點。
那麼對於枚舉的答案 來說,如何找到乘法表中有多少小於等於它的數呢?我們可以直接從 開始枚舉,和 相乘並且結果小於等於 的數有 個,當然還有個 的限制,所以是 個。然後和 相乘並且結果小於等於 的數有 個。依此類推下去,最終和 相乘並且結果小於等於 的數有 個。
所以最終小於等於 的個數 就可以計算爲:
二分法+優化
當然這題計算還可以進行一些優化。
首先第 小的數是一定小於等於 的,所以我們的二分上界可以定爲 。
其次注意到當 之後,個數一定是 ,所以 只需要枚舉到 就行了。
然後當 時,有 ,所以這部分的求和結果就是 。所以 又可以寫爲:
最後,對於某個 ,我們會發現如果 慢慢增大,某一段連續區間內 的值都是不會變的。而 最大可以增大到 ,那麼這一段區間內的求和就可以直接算出來:
接着令 直接跳轉到 就可以了,這樣就不用慢慢加 計算了。要特別注意的是最後不能超過 。
理論上這樣的計算複雜度是更低的,但是實際運行中速度還不如不加最後一步優化,可能原因是除法操作次數太多了,反而總的操作次數超過了直接遍歷計算。
代碼
二分法(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小的數: https://leetcode-cn.com/problems/kth-smallest-number-in-multiplication-table/