LeetCode-1201. 丑数 III(二分+容斥)

该博客讨论了LeetCode中的1201题,即如何找到第n个小丑数,小丑数定义为可被a、b或c整除的正整数。由于数据范围限制,直接求解会导致超时,因此采用二分查找优化算法。博主分析了如何利用容斥原理避免重复计算,计算能被a、b、c单独或共同整除的数的个数,并给出了计算能同时被多个数整除的数的方法。
摘要由CSDN通过智能技术生成

1201. 丑数 III

  • 小丑数(自己取的名字,为了跟前面的丑数和超级丑数区分开):可被a或b或c整除的正整数
    • 因子只要包含a或b或c就认为是小丑数
    • 丑数/超级丑数:只能包含
  • 思路:
    • 题目数据范围:
      • 1 <= n, a, b, c <= 10^9
      • 1 <= a * b * c <= 10^18
      • 本题结果在 [1, 2 * 10^9] 的范围内
      • 所以若用多路归并, O ( m a x ( m l o g m , n l o g m ) ) = O ( n ) O(max(mlogm,nlogm))=O(n) O(max(mlogm,nlogm))=O(n)必然会超时
        • 需要 O ( l o g n ) O(logn) O(logn)算法
    • 想到二分
      • 题目要求第n个小丑数,设这个数为num,那么可以这么看:
        • [1,...,num]范围内的小丑数有n
      • 随着num增大,[1,...num]范围内的小丑数增多
        • 随着num减小,[1,...num]范围内的小丑数减少
        • 符合单调性->可用二分
      • 所以设x为,f(x)[1,...,x]范围内的小丑数个数,那么f(x)是单调不递减函数
        • 找第n个小丑数即是找f(x)==n的左边界
        • 如何二分解决了
    • 接下来,如何找[1,...,x]范围内小丑数的个数?
      • 能被a整除的数有x/a个,能被b整除的数有x/b个,能被c整除的数有x/c
        • 但是不能直接加和,比如有的数既能被a整除又能被b整除,直接加和会重复计算
          • 需要去掉重复的部分
            • 容斥原理
      • 容斥原理:集合A、B、C内数的个数= ∣ A + B + C ∣ − ∣ A ∩ B ∣ − ∣ A ∩ C ∣ − ∣ B ∩ C ∣ + ∣ A ∩ B ∩ C ∣ |A + B + C| - |A ∩ B| - |A ∩ C| - |B ∩ C| + |A ∩ B ∩ C| A+B+CABACBC+ABC
        • 所以,[1,...,x]范围内小丑数(因子包含a或b或c的数)的个数
          • =能被a整除的数的个数+能被b整除的数的个数+能被c整除的数的个数-能同时被a、b整除的数的个数-能同时被a、c整除的数的个数-能同时被b、c整除的数的个数+能同时被a、b、c整除的数的个数
          • 能同时被a、b整除的数的个数,即x/lcm(a, b),其中lcm(a, b)是a和b的最小公倍数
            • 能同时被a、b、c整除的数的个数同理
    typedef long long ll;
    int nthUglyNumber(int n, int a, int b, int c) {
        int l = 0, r = (int)2e9;
        int ans = 1;
        while(l <= r){
            int mid = l + ((r - l) >>1);
            int k = f(mid, a, b, c);
            if(k == n){
                ans = mid;
                r = mid - 1;
            }
            else if(k < n) l = mid + 1;
            else r = mid - 1;
        }
        return ans;
    }
    int f(int x, int a, int b, int c){
        ll setA = x/a, setB = x/b, setC = x/c; //long long防止溢出
        ll setAB = x / lcm(a, b);
        ll setAC = x / lcm(a, c);
        ll setBC = x / lcm(b, c);
        ll setABC = x / lcm(a, lcm(b, c));
        return (setA + setB + setC - setAB - setAC - setBC + setABC); //容斥原理
    }
    ll lcm(ll a, ll b){
        return (a * b / gcd(a, b));
    }
    ll gcd(ll a, ll b){
        return b? gcd(b, a%b): a;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值