原文链接: https://leetcode-cn.com/problems/kth-smallest-number-in-multiplication-table
英文原文
Nearly everyone has used the Multiplication Table. The multiplication table of size m x n
is an integer matrix mat
where mat[i][j] == i * j
(1-indexed).
Given three integers m
, n
, and k
, return the kth
smallest element in the m x n
multiplication table.
Example 1:
Input: m = 3, n = 3, k = 5 Output: 3 Explanation: The 5th smallest number is 3.
Example 2:
Input: m = 2, n = 3, k = 6 Output: 6 Explanation: The 6th smallest number is 6.
Constraints:
1 <= m, n <= 3 * 104
1 <= k <= m * n
中文题目
几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第k
小的数字吗?
给定高度m
、宽度n
的一张 m * 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).
注意:
m
和n
的范围在 [1, 30000] 之间。k
的范围在 [1, m * n] 之间。
通过代码
官方题解
方法一:暴力法[超过内存限制]
算法:
创建乘法表并对其排序,然后获取 $k^{th}$ 的元素。
class Solution {
public int findKthNumber(int m, int n, int k) {
int[] table = new int[m*n];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
table[(i - 1) * n + j - 1] = i * j;
}
}
Arrays.sort(table);
return table[k-1];
}
}
class Solution(object):
def findKthNumber(self, m, n, k):
table = [i*j for i in range(1, m+1) for j in range(1, n+1)]
table.sort()
return table[k-1]
复杂度分析
- 时间复杂度:$O\big((mn))$ 创建表,然后 $O(mn\log(mn))$ 对其进行排序。
- 空间复杂度:$O(mn)$ 存储乘法表。
方法二:Next Heap[超过时间限制]
算法:
- 维护每行中最小的未使用的元素堆。然后,在堆上查找到下一个元素是一个 pop 操作。
- 我们的堆将由 $\text{(val, root)}$ 组成,其中, $\text{val}$ 是该行的下一个未使用的值,而 $\text{root}$ 是该行的起始值。
- 我们将在表中重复查找下一个最低的元素,若有则堆做一个 pop 弹出一个元素,然后再把表中查到的元素插入堆中。
class Solution {
public int findKthNumber(int m, int n, int k) {
PriorityQueue<Node> heap = new PriorityQueue<Node>(m,
Comparator.<Node> comparingInt(node -> node.val));
for (int i = 1; i <= m; i++) {
heap.offer(new Node(i, i));
}
Node node = null;
for (int i = 0; i < k; i++) {
node = heap.poll();
int nxt = node.val + node.root;
if (nxt <= node.root * n) {
heap.offer(new Node(nxt, node.root));
}
}
return node.val;
}
}
class Node {
int val;
int root;
public Node(int v, int r) {
val = v;
root = r;
}
}
class Solution(object):
def findKthNumber(self, m, n, k):
heap = [(i, i) for i in range(1, m+1)]
heapq.heapify(heap)
for _ in xrange(k):
val, root = heapq.heappop(heap)
nxt = val + root
if nxt <= root * n:
heapq.heappush(heap, (nxt, root))
return val
复杂度分析
- 时间复杂度:$O(k * m \log m) = O(m^2 n \log m)$。我们最初的操作是 $O(m)$。然后,每个 pop 和 push 都是 $O(m \log m)$ 并且我们的外循环是 $O(k) = O(mn)$
- 空间复杂度:$O(m)$,我们的堆被实现为一个包含 $m$ 元素的数组 。
方法三:二分搜索[通过]
由于 $\text{k}$ 和 $\text{m*n}$ 最多为 $9 * 10^8$,线性解将不起作用。这将激发具有 $\log$ 复杂性的解决方案,例如二分搜索。
算法:
让我们用二分搜索答案 $\text{A}$。
- 当且仅当乘法表中存在小于或等于 $\text{k}$ ,
enough(x)
才为真。通俗地说,enough(x)
描述了 $\text{x}$ 是否足够大可以成为乘法表中的 $k^{th}$ 值。 - 然后(对于我们的答案 $\text{A}$),每当 $\text{x ≥ A}$,
enough(x)
为True
;每当 $\text{x < A}$,enough(x)
为False
。 - 在二分搜索中,循环不变量
enough(hi) = True
。在开始时,enough(m*n) = True
,并且每当设置hi
时,都将其设置为“enough”(enough(mi) = True
)的值。这意味着hi
将是二分搜索结束时的最小值。 - 这样我们就可以计算出有多少值小于或等于 $\text{x}$。对于 $\text{m}$ 行中的每一行,$i^{th}$ 行看起来像是 $\text{[i, 2i, 3i, …, ni]}$.。可能出现的最大的 $\text{ki ≤ x}$ 是 $\text{k = x // i}$。但是,如果 $\text{x}$ 真的很大,那么可能是$\text{k > n}$ ,那么在该行中总共有 $\text{min(k, n) = min(x // i, n)}$ 值小于或等于 $\text{x}$ 。
class Solution {
public boolean enough(int x, int m, int n, int k) {
int count = 0;
for (int i = 1; i <= m; i++) {
count += Math.min(x / i, n);
}
return count >= k;
}
public int findKthNumber(int m, int n, int k) {
int lo = 1, hi = m * n;
while (lo < hi) {
int mi = lo + (hi - lo) / 2;
if (!enough(mi, m, n, k)) lo = mi + 1;
else hi = mi;
}
return lo;
}
}
class Solution(object):
def findKthNumber(self, m, n, k):
def enough(x):
count = 0
for i in xrange(1, m+1):
count += min(x // i, n)
return count >= k
lo, hi = 1, m * n
while lo < hi:
mi = (lo + hi) / 2
if not enough(mi):
lo = mi + 1
else:
hi = mi
return lo
复杂度分析
- 时间复杂度:$O(m * \log (mn))$。我们的二分搜索在每一步将间隔 $\text{[lo, hi]}$ 分为两部分。在每个步骤中,我们都调用了
enough
,这需要$O(m)$ 时间。 - 空间复杂度:$O(1)$ ,我们只在中间计算期间将整数保存在内存中。
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
7418 | 14466 | 51.3% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|
相似题目
题目 | 难度 |
---|---|
有序矩阵中第 K 小的元素 | 中等 |
找出第 k 小的距离对 | 困难 |
第 K 个最小的素数分数 | 困难 |