【每日算法Day 83】鄰居小孩一年級就會的乘法表,你會嗎?

題目鏈接

LeetCode 668. 乘法表中第k小的數[1]

題目描述

幾乎每一個人都用乘法表。但是你能在乘法表中快速找到第 小的數字嗎?

給定高度 、寬度 的一張 的乘法表,以及正整數 ,你需要返回表中第 小的數字。

示例1

輸入:  m = 3, n = 3, k = 5  輸出:  3  解釋:  乘法表:  1	2	3  2	4	6  3	6	9  第5小的數字是 3 (1, 2, 2, 3, 3).

示例2

輸入:  m = 2, n = 3, k = 6  輸出:  6  解釋:  乘法表:  1	2	3  2	4	6  第6小的數字是 6 (1, 2, 2, 3, 4, 6).

說明:

  • 和 的範圍在 之間。
  • 的範圍在 之間。

題解

二分法

因為 數量級是 級別的,所以顯然不能直接枚舉,要想一個對數級別的算法。

對數級別首先想到的肯定是二分了,我們二分第 小的數 ,然後求出乘法表中小於等於 的數的數量 。如果發現 ,那就說明這個答案太大了,還可以繼續縮小。否則的話答案太小了,得增大一點。

那麼對於枚舉的答案 來說,如何找到乘法表中有多少小於等於它的數呢?我們可以直接從 開始枚舉,和 相乘並且結果小於等於 的數有 個,當然還有個 的限制,所以是 個。然後和 相乘並且結果小於等於 的數有 個。依此類推下去,最終和 相乘並且結果小於等於 的數有 個。

所以最終小於等於 的個數 就可以計算為:

二分法+優化

當然這題計算還可以進行一些優化。

首先第 小的數是一定小於等於 的,所以我們的二分上界可以定為 。

其次注意到當 之後,個數一定是 ,所以 只需要枚舉到 就行了。

然後當 時,有 ,所以這部分的求和結果就是 。所以 又可以寫為:

最後,對於某個 ,我們會發現如果 慢慢增大,某一段連續區間內 的值都是不會變的。而 最大可以增大到 ,那麼這一段區間內的求和就可以直接算出來:

接着令 直接跳轉到 就可以了,這樣就不用慢慢加 計算了。要特別注意的是最後不能超過 。

理論上這樣的計算複雜度是更低的,但是實際運行中速度還不如不加最後一步優化,可能原因是除法操作次數太多了,反而總的操作次數超過了直接遍歷計算。

代碼

二分法(c++)

class Solution {  public:      int findKthNumber(int m, int n, int k) {          int l = 1, r = m*n;          while (l < r) {              int mid = l+((r-l)>>1);              if (enough(mid, m, n, k)) r = mid;              else l = mid+1;          }          return l;      }        bool enough(int x, int m, int n, int k) {          int cnt = 0;          for (int i = 1; i <= m; ++i) {              cnt += x/i<n?x/i:n;          }          return cnt >= k;      }  };

二分法+優化(c++)

class Solution {  public:      int findKthNumber(int m, int n, int k) {          int l = 1, r = k;          while (l < r) {              int mid = l+((r-l)>>1);              if (enough(mid, m<mid?m:mid, n<mid?n:mid, k)) r = mid;              else l = mid+1;          }          return l;      }        bool enough(int x, int m, int n, int k) {          int cnt = n*(x/n), d = 0;          for (int i = (x/n)+1; i <= m; i = d+1) {              d = x/(x/i);              cnt += (x/i)*((d<m?d:m)-i+1);          }          return cnt >= k;      }  };

二分法(python)

class Solution:      def findKthNumber(self, m: int, n: int, k: int) -> int:          def enough(x, m, n, k):              cnt = 0              for i in range(1, m+1):                  cnt += x//i if x//i<n else n              return cnt >= k            l, r = 1, m*n          while l < r:              mid = l+((r-l)>>1)              if enough(mid, m, n, k): r = mid              else: l = mid+1          return l

二分法+優化(python)

class Solution:      def findKthNumber(self, m: int, n: int, k: int) -> int:          def enough(x, m, n, k):              cnt, i, d = n*(x//n), x//n+1, 0              while i <= m:                  d = x//(x//i)                  cnt += (x//i)*((d if d<m else m)-i+1)                  i = d+1              return cnt >= k            l, r = 1, k          while l < r:              mid = l+((r-l)>>1)              if enough(mid, m if m<mid else mid, n if n<mid else mid, k): r = mid              else: l = mid+1          return l

參考資料

[1]

LeetCode 668. 乘法表中第k小的數: https://leetcode-cn.com/problems/kth-smallest-number-in-multiplication-table/

作者簡介:godweiyang知乎同名華東師範大學計算機系碩士在讀,方向自然語言處理與深度學習。喜歡與人分享技術與知識,期待與你的進一步交流~