二分查找题目:乘法表中第 k 小的数

题目

标题和出处

标题:乘法表中第 k 小的数

出处:668. 乘法表中第 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:

示例 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:

示例 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} 1m, n3×104
  • 1 ≤ k ≤ m × n \texttt{1} \le \texttt{k} \le \texttt{m} \times \texttt{n} 1km×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 1kmn,乘法表中第 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值