题目来源:https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/
大致题意:
给整数 m 、n 和 k,表示有一个 m*n 的乘法表矩阵,求乘法表中第 k 小的数
思路
给定一个数 t,如果要在给定的乘法表中求小于等于 t 的数个数,可以通过如下方法统计
一行一行的进行比较,设当前比较的行数为 i,乘法表的列数为 r,那么
- 若 t > i * r,则该行元素全部小于 t
- 若 t <= i * r,如果 t 可以被 i 整除(也就是该行有等于 t 的元素),那么小于 t 的元素有 t / i - 1 个,也就是说小于等于 t 的数有 t / i 个;如果不可以被整除,小于等于 t 的个数也是 t / i 个
于是可以通过二分枚举所有数,直到枚举的数是乘法表中的第 k 小的数为止,这样时间复杂度为 O(min(m, n)log(mn))
具体的执行逻辑可以概括为:
- 初始化二分的左边界为 1,右边界为 m*n
- 获取当前区间中值,使用上述方法统计乘法表中小于等于中值的元素个数
- 如果当前乘法表中小于等于中值的元素个数大于等于 k,表示当前中值大于等于第 k 小的数,使用当前中值作为新的右边界;否则,表示当前中值小于第 k 小的数,应该往更大的区间找,使用中值 + 1 作为新的左边界
- 重复 2 、3步,直至当前左右边界相等
可以注意到,比较时所取的中值可能不在乘法表对应的积中,那么此时中值与所求积的关系有两种
- 中值大于所求积,那么会更新右边界为中值,也就会缩小下次计算的中值
- 中值大于所求积,那么会更新左边界为中值 + 1,也就会增加下次计算的中值
以上两种情况会不断缩小查询区间,迫使区间边界向中值靠近,直至区间长度 1,此时区间内的值也就为所求积
代码:
public int findKthNumber(int m, int n, int k) {
int min = Math.min(m, n); // 求出 m 和 n 中的最小值,使用最小值做乘法表的行
int max = Math.max(m, n); // 求出 m 和 n 中的最大值,使用最大值做乘法表的列
// 二分边界
int l = 1;
int r = m * n;
while (l < r) {
// 当前边界的中间值
int mid = (l + r) >> 1;
// 获取乘法表小于等于中间值的数的个数
int count = getCount(mid, min, max);
// 如果个数小于 k,表示当前中间值比第 k 小的数小,更新左边界
if (count < k) {
l = mid + 1;
} else { // 个数大于等于 k,表示当前中间值大于等于第 k 小的数,更新右边界
r = mid;
}
}
return l;
}
public int getCount(int mid, int min, int max) {
int a = 0;
// 获取乘法表小于等于中间值的数的个数
for (int i = 1; i <= min; i++) {
// 当前行最大值小于 mid,直接统计该行所有元素
if (i * max < mid) {
a += max;
} else { // 否则,统计小于等于 mid 的元素个数
a += mid / i;
}
}
return a;
}