Hard
题目描述
几乎每个人都使用过乘法表。大小为 m x n 的乘法表是一个整数矩阵 mat,其中 mat[i][j] == i * j(1-indexed)。
给你三个整数 m、n 和 k,请你返回大小为 m × n 的乘法表中第 k 小的数。
示例 1:
输入:m = 3, n = 3, k = 5
输出:3
解释:第 5 小的数字是 3。
示例 2:
输入:m = 2, n = 3, k = 6
输出:6
解释:第 6 小的数字是 6。
提示:
- 1 <= m, n <= 3 * 10^4
- 1 <= k <= m * n
解题思路
解题思路
这道题要求在 m×n 的乘法表中找到第 k 小的元素。如果直接生成整个乘法表然后排序,会消耗大量内存和时间。
关键观察:
- 乘法表中最小值是 1,最大值是 m×n
- 对于任意值 x,我们可以快速计算出乘法表中小于等于 x 的元素个数
- 在第 i 行中,小于等于 x 的元素个数为 min(n, x/i)
二分查找解法: 我们可以对答案进行二分查找。对于候选答案 mid,计算乘法表中小于等于 mid 的元素个数:
- 如果个数 >= k,说明第 k 小的数 <= mid,在左半部分继续查找
- 如果个数 < k,说明第 k 小的数 > mid,在右半部分继续查找
计算小于等于 x 的元素个数: 遍历每一行 i(1到m),在第 i 行中,小于等于 x 的元素个数为 min(n, x/i)。
时间复杂度为 O(m * log(m*n)),空间复杂度为 O(1),是最优解法。
代码实现
class Solution {
public:
int findKthNumber(int m, int n, int k) {
int left = 1, right = m * n;
while (left < right) {
int mid = left + (right - left) / 2;
int count = countLessEqual(m, n, mid);
if (count >= k) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
private:
int countLessEqual(int m, int n, int target) {
int count = 0;
for (int i = 1; i <= m; i++) {
count += min(n, target / i);
}
return count;
}
};
class Solution:
def findKthNumber(self, m: int, n: int, k: int) -> int:
def count_less_equal(target):
count = 0
for i in range(1, m + 1):
count += min(n, target // i)
return count
left, right = 1, m * n
while left < right:
mid = left + (right - left) // 2
if count_less_equal(mid) >= k:
right = mid
else:
left = mid + 1
return left
public class Solution {
public int FindKthNumber(int m, int n, int k) {
int left = 1, right = m * n;
while (left < right) {
int mid = left + (right - left) / 2;
int count = CountLessEqual(m, n, mid);
if (count >= k) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
private int CountLessEqual(int m, int n, int target) {
int count = 0;
for (int i = 1; i <= m; i++) {
count += Math.Min(n, target / i);
}
return count;
}
}
var findKthNumber = function(m, n, k) {
const countLessEqual = (target) => {
let count = 0;
for (let i = 1; i <= m; i++) {
count += Math.min(n, Math.floor(target / i));
}
return count;
};
let left = 1, right = m * n;
while (left < right) {
const mid = left + Math.floor((right - left) / 2);
if (countLessEqual(mid) >= k) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(m * log(m*n)) | 二分查找进行 log(m*n) 次,每次计算需要 O(m) |
| 空间复杂度 | O(1) | 只使用常数级额外空间 |