题目:
Nearly every one have used the Multiplication Table. But could you find out the k-th
smallest number quickly from the multiplication table?
Given the height m
and the length n
of a m * n
Multiplication Table, and a positive integer k
, you need to return the k-th
smallest number in this table.
Example 1:
Input: m = 3, n = 3, k = 5 Output: Explanation: The Multiplication Table: 1 2 3 2 4 6 3 6 9 The 5-th smallest number is 3 (1, 2, 2, 3, 3).
Example 2:
Input: m = 2, n = 3, k = 6 Output: Explanation: The Multiplication Table: 1 2 3 2 4 6 The 6-th smallest number is 6 (1, 2, 2, 3, 4, 6).
Note:
- The
m
andn
will be in the range [1, 30000]. - The
k
will be in the range [1, m * n]
思路:
这道题目很有意思。我记得Leetcode上有一道类似的题目,不过实在记不清楚原题的编号了。这里给出我想出来的三个解决方案,其中前两个解决方案都没法通过所有测试,但是可以用来开阔思路。
1、Naive Heap:
思路是我们将乘法表中的所有结果都提前存入一个小顶堆中,然后依次从堆中取数,当取到第k个元素时,返回当前被取出的堆顶元素即可。该算法的时间复杂度是O(m*n + klogmn),空间复杂度是O(m*n)。这个空间复杂度过高,造成“Memory Limit Exceeded”。
2、Smart Heap:
仔细观察乘法表可以发现,其每行、每列都是有序的。所以其实我们并不需要提前存储所有的乘法结果:当我们取出当前堆顶元素之后,下一个最小元素要么位于该元素的正右边,要么位于该元素的正下边。所以我们首先在小顶堆中只放入第1个元素,然后每次取出堆顶元素之后,在堆中放入它的正右边元素和正下边元素(如果有的话)。当取到第k个元素时返回即可。该算法的空间复杂度可以降低到O(m)(因为每行元素我们最多存一个)。时间复杂度也可以降低到O(klogm)。然而这个时间复杂度仍然不能通过所有测试数据,造成“Time Limit Exceeded”。
3、Binary Search:
杀手锏是使用二分查找法:在该乘法表中,最小值是1,最大值是m*n。所以我们初始化left = 1, right = m * n。每次首先得到left和right的中值mid,然后计算比mid小的数一种有多少个,而这个可以用O(m)的时间复杂度即可获得。如果比mid小的数小于k,说明我们要求的数在区间[mid + 1, right]内;否则说明我们要求的数在[left, mid - 1]。最终当left > right的时候,left即为我们所求。该算法的空间复杂度是O(1),时间复杂度是O(nlog(m*n))。由于在测试用例中k往往大于n,相当于n^2的量级,所以相比于Smart Heap,该算法的时间复杂度也要低一个量级。
代码:
1、Naive Heap:
class Solution {
public:
int findKthNumber(int m, int n, int k) {
vector<int> nums;
for (int r = 1; r <= m; ++r) {
for (int c = 1; c <= n; ++c) {
nums.push_back(-r * c);
}
}
make_heap(nums.begin(), nums.end());
int index = 0;
while (true) {
if (++index == k) {
return -nums[0];
}
pop_heap(nums.begin(), nums.end());
nums.pop_back();
}
return nums.back();
}
};
2、Smart Heap:
class Solution {
public:
int findKthNumber(int m, int n, int k) {
vector<pair<int, int>> hp;
hp.push_back(make_pair(1, 1));
int index = 0;
while (true) {
pair<int, int> p = hp[0];
pop_heap(hp.begin(), hp.end(), compare);
hp.pop_back();
if (++index == k) {
return p.first * p.second;
}
if (p.second < n) {
hp.push_back(make_pair(p.first, p.second + 1));
push_heap(hp.begin(), hp.end(), compare);
}
if (p.second == 1 && p.first < m) {
hp.push_back(make_pair(p.first + 1, 1));
push_heap(hp.begin(), hp.end(), compare);
}
}
return m * n;
}
private:
static bool compare(pair<int, int> &a, pair<int, int> &b) {
return a.first * a.second > b.first * b.second;
}
};
3、Binary Search:
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 num = 0;
for (int r = 1; r <= m; ++r) {
num += min(n, mid / r);
}
if (num < k) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return left;
}
};