题目
标题和出处
标题:乘法表中第 k 小的数
难度
7 级
题目描述
要求
几乎每一个人都使用过乘法表。大小为 m × n \texttt{m} \times \texttt{n} m×n 的乘法表是一个整数矩阵 mat \texttt{mat} mat,其中 mat[i][j] = i × j \texttt{mat[i][j]} = \texttt{i} \times \texttt{j} mat[i][j]=i×j(下标从 1 \texttt{1} 1 开始)。
给定三个整数 m \texttt{m} m、 n \texttt{n} n 和 k \texttt{k} k,返回 m × n \texttt{m} \times \texttt{n} m×n 的乘法表中的第 k \texttt{k} k 小的元素。
示例
示例 1:
输入:
m
=
3,
n
=
3,
k
=
5
\texttt{m = 3, n = 3, k = 5}
m = 3, n = 3, k = 5
输出:
3
\texttt{3}
3
解释:第
5
\texttt{5}
5 小的数字是
3
\texttt{3}
3。
示例 2:
输入:
m
=
2,
n
=
3,
k
=
6
\texttt{m = 2, n = 3, k = 6}
m = 2, n = 3, k = 6
输出:
6
\texttt{6}
6
解释:第
6
\texttt{6}
6 小的数字是
6
\texttt{6}
6。
数据范围
- 1 ≤ m, n ≤ 3 × 10 4 \texttt{1} \le \texttt{m, n} \le \texttt{3} \times \texttt{10}^\texttt{4} 1≤m, n≤3×104
- 1 ≤ k ≤ m × n \texttt{1} \le \texttt{k} \le \texttt{m} \times \texttt{n} 1≤k≤m×n
解法
思路和算法
计算 m × n m \times n m×n 的乘法表中的第 k k k 小的数,最朴素方法是生成完整的乘法表,然后遍历乘法表寻找第 k k k 小的数。由于 m m m 和 n n n 的最大值是 3 × 1 0 4 3 \times 10^4 3×104,因此乘法表中最多有 9 × 1 0 8 9 \times 10^8 9×108 个数,该数据规模下生成乘法表的做法是不可行的,必须使用时间复杂度更低的做法。
考虑范围 [ 1 , m n ] [1, mn] [1,mn] 中的整数 x x x, x x x 不一定在乘法表中。如果乘法表中小于等于 x x x 的数至少有 k k k 个,则乘法表中第 k k k 小的数小于等于 x x x;如果乘法表中小于等于 x x x 的数少于 k k k 个,则乘法表中第 k k k 小的数大于 x x x。因此,这道题是二分查找判定问题,需要找到最小的整数 x x x 使得乘法表中小于等于 x x x 的数至少有 k k k 个。
用 low \textit{low} low 和 high \textit{high} high 分别表示二分查找的下界和上界。由于 1 ≤ k ≤ m n 1 \le k \le mn 1≤k≤mn,乘法表中第 k k k 小的数一定在范围 [ 1 , m n ] [1, mn] [1,mn] 中,因此 low \textit{low} low 的初始值等于 1 1 1, high \textit{high} high 的初始值等于 m n mn mn。
当正整数 x x x 已知时,为了计算 m × n m \times n m×n 的乘法表中小于等于 x x x 的数的个数,需要计算乘法表的每一行中小于等于 x x x 的数的个数。假设乘法表的列数无限,乘法表的第 i i i 行中的每个数都是 i i i 的倍数,第 i i i 行中小于等于 x x x 的数的个数是 ⌊ x i ⌋ \Big\lfloor \dfrac{x}{i} \Big\rfloor ⌊ix⌋。考虑到乘法表有 n n n 列,第 i i i 行中小于等于 x x x 的数的个数不超过 n n n。因此乘法表的第 i i i 行中小于等于 x x x 的数的个数是 min ( ⌊ x i ⌋ , n ) \min(\Big\lfloor \dfrac{x}{i} \Big\rfloor, n) min(⌊ix⌋,n)。在计算乘法表的每一行中小于等于 x x x 的数的个数之后,即可得到乘法表中小于等于 x x x 的数的个数。
每次查找时,取 mid \textit{mid} mid 为 low \textit{low} low 和 high \textit{high} high 的平均数向下取整,计算 m × n m \times n m×n 的乘法表中小于等于 mid \textit{mid} mid 的数的个数,执行如下操作。
-
如果乘法表中小于等于 mid \textit{mid} mid 的数的个数至少有 k k k 个,则乘法表中第 k k k 小的数小于等于 mid \textit{mid} mid,因此在 [ low , mid ] [\textit{low}, \textit{mid}] [low,mid] 中继续查找。
-
如果乘法表中小于等于 mid \textit{mid} mid 的数的个数少于 k k k 个,则乘法表中第 k k k 小的数大于 mid \textit{mid} mid,因此在 [ mid + 1 , high ] [\textit{mid} + 1, \textit{high}] [mid+1,high] 中继续查找。
当 low = high \textit{low} = \textit{high} low=high 时,查找结束,此时 low \textit{low} low 即为乘法表中第 k k k 小的数。
执行二分查找的次数是 O ( log ( m n ) ) O(\log (mn)) O(log(mn)),每次二分查找需要 O ( m ) O(m) O(m) 的时间计算小于等于特定值的数的个数,因此总时间复杂度是 O ( m log ( m n ) ) O(m \log (mn)) O(mlog(mn))。
实现方面,由于将 m × n m \times n m×n 的乘法表转置之后即可得到 n × m n \times m n×m 的乘法表,因此当 m > n m > n m>n 时,可以在转置后的乘法表中寻找第 k k k 小的数,将时间复杂度从 O ( m log ( m n ) ) O(m \log (mn)) O(mlog(mn)) 降到 O ( n log ( m n ) ) O(n \log (mn)) O(nlog(mn))。
代码
class Solution {
public int findKthNumber(int m, int n, int k) {
if (m > n) {
int temp = m;
m = n;
n = temp;
}
int low = 1, high = m * n;
while (low < high) {
int mid = low + (high - low) / 2;
int rank = getRank(mid, m, n);
if (rank >= k) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
public int getRank(int num, int m, int n) {
int rank = 0;
for (int i = 1; i <= m; i++) {
rank += Math.min(num / i, n);
}
return rank;
}
}
复杂度分析
-
时间复杂度: O ( min ( m , n ) log ( m n ) ) O(\min(m, n) \log (mn)) O(min(m,n)log(mn)),其中 m m m 和 n n n 分别是乘法表的行数和列数。需要执行 O ( log ( m n ) ) O(\log (mn)) O(log(mn)) 次二分查找,每次二分查找需要 O ( min ( m , n ) ) O(\min(m, n)) O(min(m,n)) 的时间计算小于等于特定值的数的个数,时间复杂度是 O ( min ( m , n ) log ( m n ) ) O(\min(m, n) \log (mn)) O(min(m,n)log(mn))。
-
空间复杂度: O ( 1 ) O(1) O(1)。