加载中...
1201-丑数 III(Ugly Number III)
发表于:2021-12-03 | 分类: 中等
字数统计: 358 | 阅读时长: 1分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/ugly-number-iii

英文原文

An ugly number is a positive integer that is divisible by a, b, or c.

Given four integers n, a, b, and c, return the nth ugly number.

 

Example 1:

Input: n = 3, a = 2, b = 3, c = 5
Output: 4
Explanation: The ugly numbers are 2, 3, 4, 5, 6, 8, 9, 10... The 3rd is 4.

Example 2:

Input: n = 4, a = 2, b = 3, c = 4
Output: 6
Explanation: The ugly numbers are 2, 3, 4, 6, 8, 9, 10, 12... The 4th is 6.

Example 3:

Input: n = 5, a = 2, b = 11, c = 13
Output: 10
Explanation: The ugly numbers are 2, 4, 6, 8, 10, 11, 12, 13... The 5th is 10.

Example 4:

Input: n = 1000000000, a = 2, b = 217983653, c = 336916467
Output: 1999999984

 

Constraints:

  • 1 <= n, a, b, c <= 109
  • 1 <= a * b * c <= 1018
  • It is guaranteed that the result will be in range [1, 2 * 109].

中文题目

给你四个整数:nabc ,请你设计一个算法来找出第 n 个丑数。

丑数是可以被 a  b  c 整除的 正整数

 

示例 1:

输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。

示例 2:

输入:n = 4, a = 2, b = 3, c = 4
输出:6
解释:丑数序列为 2, 3, 4, 6, 8, 9, 10, 12... 其中第 4 个是 6。

示例 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
输出:1999999984

 

提示:

  • 1 <= n, a, b, c <= 10^9
  • 1 <= a * b * c <= 10^18
  • 本题结果在 [1, 2 * 10^9] 的范围内

通过代码

高赞题解

基础思路

首先,为什么第一时间能想到二分法?

让我们观察题目,可以看到,最终状态(即n)的范围非常大。试图自底向上递推或是按照通常的自顶向下回溯显然会超时(比如动态规划、DFS等方法)

面对这么大的状态空间,二分法的时间复杂度是logN,因此能够大大压缩需要遍历的状态数目

思路剖析

既然已经确定了二分法作为切入点,关键问题来了,如何二分呢?

按照题意,所谓丑数是可以至少被a、b、c三者中的一者整除的,那么对于一个丑数X,我们能够确定它是第几个丑数吗?

–答案显然是可以的,我们只需要计算X中包含了多少个丑数因子即可。

即只需要知道在[0,X]范围内,还有多少个丑数即可,而这些丑数,无非就是一些能被a或者b或者c所整除的数。

那么显然,我们直接用X/a、X/b、X/c就能计算出[0,X]范围内有多少数能被a或者b或者c整除,然后把它们加起来就是答案!

但是仔细思考一下,我们是不是重复计算了些什么?如果一个数既能被a整除,又能被b整除,那么实际上该数在先前的计算中就被重复计算了一次(分别是在计算X/a和X/b时)。

–好吧,让我们思考所有可能的情况

1.该数只能被a整除 (该数一定是a 的整数倍)

2.该数只能被b整除 (该数一定是b 的整数倍)

3.该数只能被c整除 (该数一定是c 的整数倍)

4.该数只能被a和b同时整除 (该数一定是a、b最小公倍数的整数倍)

5.该数只能被a和c同时整除 (该数一定是a、c最小公倍数的整数倍)

6.该数只能被b和c同时整除 (该数一定是b、c最小公倍数的整数倍)

7.该数只能被a和b和c同时整除(该数一定是a、b、c的最小公倍数的整数倍)

所以,我们只需要分别计算以上七项就能得到结果了!让我们分别来看(用MCM+下标表示最小公倍数):

情况1 = X/a - 情况4 - 情况5 - 情况7
情况2 = X/b - 情况4 - 情况6 - 情况7
情况3 = X/c - 情况5 - 情况6 - 情况7
情况4 = X/MCM_a_b - 情况7
情况5 = X/MCM_a_c - 情况7
情况6 = X/MCM_b_c - 情况7
情况7 = X/MCM_a_b_c

让我们整理上述方程后也就得到:

sum(情况) = X/a + X/b + X/c - X/MCM_a_b - X/MCM_a_c - X/MCM_b_c + X/MCM_a_b_c

好了,现在也就得到了计算X中包含多少个丑数因子的方法了!

至于计算最小公倍数的方法,这里不多介绍,概括而言就是对于两个数a和b,它们的最小公倍数 = a*b/(a和b的最大公约数),最大公约数可以通过辗转相除法得到

二分搜索

在得到了计算任意数中包含了多少个丑数因子的方法后,我们实际上只需要通过二分法,不断缩小边界范围,直到某个位置所对应的数恰好包含了n个丑数因子为止。

注意,通过二分法计算的答案并非是最终答案,因为可以有很多数同时包含有n个丑数因子!

比如第n个丑数是X,那么[X,X + min(a,b,c))这个半开区间内的所有数都同时包含n个丑数因子,我们通过二分法得到的答案也随机分布于这个区间中。而实际上我们只需要得到该区间的左端即可。处理方法很简单:假设我们得到的临时答案是K(K∈[X,X + min(a,b,c))),那么K - min(K%a,K%b,K%c) = X.也就是只需要把临时答案减去其与a、b、c三者中取余的最小值即可!

代码

class Solution {
public:
    using LL = long long;

    int nthUglyNumber(int n, int a, int b, int c) {
        //看到n的范围应该马上联想到是,典型的二分思路
        LL low = min(min(a,b),c);                            //下边界显然是a、b、c中最小者
        LL high = static_cast<LL>(low) * n;                 //上边界是这个最小者的n倍
        
        LL res = Binary_Search(low,high,a,b,c,n);

        LL left_a = res%a;
        LL left_b = res%b;
        LL left_c = res%c;

        return res - min(left_a,min(left_b,left_c));
    }
    
    //二分搜索
    LL Binary_Search(LL low,LL high,int a,int b,int c,LL n){
        if(low >= high) return low;

        LL mid = (low + high)>>1;

        LL MCM_a_b = MCM(a,b);
        LL MCM_a_c = MCM(a,c);
        LL MCM_b_c = MCM(b,c);
        LL MCM_a_b_c = MCM(MCM_a_b,c);

        //独立的丑数个数为,当前数分别除以a、b、c的和,减去当前数除以a、b、c两两间最小公倍数的和,再加上当前数除以 a、b、c三者的最小公倍数
        LL count_n = mid/a + mid/b + mid/c - mid/MCM_a_b - mid/MCM_b_c - mid/MCM_a_c +  mid/MCM_a_b_c;
        
        if(count_n == n) return mid;
        
        if(count_n < n) return Binary_Search(mid + 1,high,a,b,c,n);

        return Binary_Search(low,mid-1,a,b,c,n);
    }

    //求最小公倍数:两数乘积除以最大公约数
    LL MCM(LL a,LL b){
        
        LL Multi = a * b;
        
        while(b > 0){
            LL tmp = a % b;
            a = b;
            b = tmp;
        }

        return Multi/a;
    }

};

统计信息

通过次数 提交次数 AC比率
6337 25434 24.9%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
1944-队列中可以看到的人数(Number of Visible People in a Queue)
下一篇:
1202-交换字符串中的元素(Smallest String With Swaps)
本文目录
本文目录