LeetCode 5198. 醜數 III(Java)容斥原理和二分查找

題目連接:5198. 醜數 IIIhtml

請你幫忙設計一個程序,用來找出第 n 個醜數。java

醜數是能夠被 abc 整除的 正整數web

示例 1:

輸入:n = 3, a = 2, b = 3, c = 5
輸出:4
解釋:醜數序列爲 2, 3, 4, 5, 6, 8, 9, 10… 其中第 3 個是 4。app

示例 2:

輸入:n = 4, a = 2, b = 3, c = 4
輸出:6
解釋:醜數序列爲 2, 3, 4, 6, 8, 9, 12… 其中第 4 個是 6。svg

示例 3:

輸入:n = 5, a = 2, b = 11, c = 13
輸出:10
解釋:醜數序列爲 2, 4, 6, 8, 10, 11, 12, 13… 其中第 5 個是 10。測試

示例 4:

輸入:n = 1000000000, a = 2, b = 217983653, c = 336916467
輸出:1999999984spa

提示:
  • 1 <= n, a, b, c <= 10^9
  • 1 <= a * b * c <= 10^18
  • 本題結果在 [1, 2 * 10^9] 的範圍內
前言:

這是我第一次碰見 醜數 這個概念。設計

若是以前見過醜數的同窗可能會發現,此題的醜數定義和【醜數 - 百度百科】的定義是不一樣的。code

此題的醜數定義:醜數是能夠被 abc 整除的正整數。xml

題目比較坑的一點是示例 2 中的醜數序列是錯的,它缺乏 10。由於 10 能被 2 整除,因此 10 是醜數。

原本就沒見過醜數,它還弄個錯誤的示例,致使寫題的時候個人思路偏離。

題解:

知道醜數的定義,那麼能夠採用暴力搜索的方法找到第 n 個醜數。可是測試示例 4 的時候會 TLE。

所以需另尋他法。

給定一個數 n,和三個數 235,那麼區間 [1, n] 有多少個醜數呢?

根據定義:咱們知道 2 的倍數確定是醜數,有多少個 2 的倍數呢?固然是 n / 2 個啦(所有向下取整)。
同理 3 的倍數也是,有 n / 3 個。
5 的倍數有 n / 5 個。

如今你會發現另外一個問題,好比 62 的倍數,也是 3 的倍數。那豈不是計算了兩遍?沒錯,確實算了兩遍。所以咱們須要知道 【容斥原理 - 百度百科】。提及來陌生,可是我相信你們都用過。

所以,咱們能夠知道區間 [1, n] 的醜數個數了。即

n u m = n a + n b + n c n b c n a c n a b + n a b c num=\lfloor\frac{n}{a}\rfloor+\lfloor\frac{n}{b}\rfloor+\lfloor\frac{n}{c}\rfloor-\lfloor\frac{n}{bc}\rfloor-\lfloor\frac{n}{ac}\rfloor-\lfloor\frac{n}{ab}\rfloor+\lfloor\frac{n}{abc}\rfloor

代碼表示爲:num = n / a + n / b + n / c - n / bc - n / ac - n / ab + n / abc

須要注意的是,題目沒有說給的三個數是互質的,所以須要計算最小公倍數和最大公約數。

而後再用【二分查找 - 百度百科】逼近第 n 個醜數 U n U_n

時間複雜度: 二分查找時間複雜度爲 O ( l o g 2 n ) O(log2n)
空間複雜度: O ( 1 ) O(1)

Java:
class Solution {
	public int nthUglyNumber(	int n,
								int a,
								int b,
								int c) {
		long ab = lcm(a, b);// a,b的最小公倍數
		long ac = lcm(a, c);
		long bc = lcm(b, c);
		long abc = lcm(ab, c);

		long left = 1, right = 2000000000;
		while (left < right) {
			long mid = left + right >> 1;// 中間值,+優先級高於>>
			// 利用容斥原理計算區間[1, mid]的醜數
			long num = mid / a + mid / b + mid / c;
			num -= mid / bc + mid / ac + mid / ab;
			num += mid / abc;
			if (num < n) {
				left = mid + 1;// 取區間[mid + 1, right]
			} else {
				right = mid;// 取區間[left, mid]
			}
		}
		return (int) left;
	}

	// 計算最小公倍數
	private long lcm(	long a,
						long b) {
		return a / gcd(a, b) * b;// 須要調用gcd
	}

	// 計算最大公約數
	private long gcd(	long a,
						long b) {
		return b > 0 ? gcd(b, a % b) : a;
	}
}
相關文章
相關標籤/搜索