Leetcode丑数序列

Posted 梦想闹钟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode丑数序列相关的知识,希望对你有一定的参考价值。

小于等于某个整数的丑数个数很好计算,即n/a+n/b+n/c-n/ab的最小公倍数-n/ac的最小公倍数-n/bc的最小公倍数+n/三者的最小公倍数
只需要在解空间内二分找到一个整数ans,使得[1, ans]内恰好有n个丑数,ans即为第n个丑数

from math import gcd
class Solution:
    def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int:
        #三数最小公倍数=两数最小公倍数与第三个数的最小公倍数
        def Lcm3(x,y,z):
            a = (x*y)//gcd(x,y)  
            return (a*z)//gcd(a,z)
        '''
        计算有多少个丑数小于等于x
        + x整除a,b,c -整除ab,bc,ac最小公倍数 +整除abc最小公倍数 即为所求
        '''
        def uglynum(x):
                return x//a+x//b+x//c-x//(a*b//gcd(a,b))-x//(a*c//gcd(a,c))-x//(b*c//gcd(b,c))+x//Lcm3(a,b,c)
        '''
        二分搜索,注意只要uglynum(mid)<n left就=mid+1 所以最后得到的left就是所求
        例如测试用例2中  a=2,b=3,c=4
        括号中为丑数                    1,(2),(3),(4),5,(6),7,(8)
        小于等于它们的丑数个数分别为     0, 1 , 2 , 3 ,3, 4, 4, 5 
        若n==4
        如果uglynum(mid)<4 则left一定能直接取到6而不是7
        '''
        left=1
        right=n*min(a,b,c)
        while left<right:
            mid=(left+right)//2
            if uglynum(mid)<n:
                left=mid+1
            else:
                right=mid
        return left

作者:CTSCSU
链接:https://leetcode.cn/problems/ugly-number-iii/solution/python-er-fen-cha-zhao-by-ctscsu/
来源:力扣(LeetCode)

LeetCode——264. 丑数 II

编写一个程序,找出第 n 个丑数。

丑数就是只包含质因数 2, 3, 5 的正整数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

说明:

1 是丑数。
n 不超过1690。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ugly-number-ii

1.暴力(brute force)

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> v;
        for (long long a=1;a<=INT_MAX;a=a*2)
            for (long long b=a;b<=INT_MAX;b=b*3)
                for (long long c=b;c<=INT_MAX;c=c*5)
                    v.push_back(c);
        sort(v.begin(),v.end());
        return v.at(n-1);
    }
};

2.优先队列(小顶堆)

c++优先队列(priority_queue)用法详解

https://blog.csdn.net/weixin_36888577/article/details/79937886

既然是队列那么先要包含头文件#include , 他和queue不同的就在于我们可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队

优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的

和队列基本操作相同:

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
一般是:

//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

基本类型例子:

#include<iostream>
#include <queue>
using namespace std;
int main() 
{
    //对于基础类型 默认是大顶堆
    priority_queue<int> a; 
    //等同于 priority_queue<int, vector<int>, less<int> > a;
    

	//    这里一定要有空格,不然成了右移运算符↓
	priority_queue<int, vector<int>, greater<int> > c;  //这样就是小顶堆
	priority_queue<string> b;

	for (int i = 0; i < 5; i++) 
	{
    	a.push(i);
    	c.push(i);
	}
	while (!a.empty()) 
	{
    	cout << a.top() << \' \';
    	a.pop();
	} 
	cout << endl;

	while (!c.empty()) 
	{
    	cout << c.top() << \' \';
    	c.pop();
	}
	cout << endl;

	b.push("abc");
	b.push("abcd");
	b.push("cbd");
	while (!b.empty()) 
	{
    	cout << b.top() << \' \';
    	b.pop();
	} 
	cout << endl;
	return 0;
}

输出

输出

4 3 2 1 0
0 1 2 3 4
cbd abcd abc

2.pari的比较,先比较第一个元素,第一个相等比较第二个

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() 
{
    priority_queue<pair<int, int> > a;
    pair<int, int> b(1, 2);
    pair<int, int> c(1, 3);
    pair<int, int> d(2, 5);
    a.push(d);
    a.push(c);
    a.push(b);
    while (!a.empty()) 
    {
        cout << a.top().first << \' \' << a.top().second << \'\\n\';
        a.pop();
    }
}

输出

2 5
1 3
1 2

3.对于自定义类型

#include <iostream>
#include <queue>
using namespace std;

//方法1
struct tmp1 //运算符重载<
{
    int x;
    tmp1(int a) {x = a;}
    bool operator<(const tmp1& a) const
    {
        return x < a.x; //大顶堆
    }
};

//方法2
struct tmp2 //重写仿函数
{
    bool operator() (tmp1 a, tmp1 b) 
    {
        return a.x < b.x; //大顶堆
    }
};

int main() 
{
    tmp1 a(1);
    tmp1 b(2);
    tmp1 c(3);
    priority_queue<tmp1> d;
    d.push(b);
    d.push(c);
    d.push(a);
    while (!d.empty()) 
    {
        cout << d.top().x << \'\\n\';
        d.pop();
    }
    cout << endl;

	priority_queue<tmp1, vector<tmp1>, tmp2> f;
	f.push(c);
	f.push(b);
	f.push(a);
	while (!f.empty()) 
	{
    	cout << f.top().x << \'\\n\';
    	f.pop();
	}
}

输出

3
2
1

3
2
1

利用优先队列有自动排序的功能
每次取出队头元素,存入队头元素2、队头元素3、队头元素5
但注意,像12这个元素,可由4乘3得到,也可由6乘2得到,所以要注意去重

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue <double,vector<double>,greater<double> > q;
        double answer=1;
        for (int i=1;i<n;++i)
        {
            q.push(answer*2);
            q.push(answer*3);
            q.push(answer*5);
            answer=q.top();
            q.pop();
            while (!q.empty() && answer==q.top())
                q.pop();
        }
        return answer;
    }
};

还可以更进一步采用set来识别有无重复

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue <double,vector<double>,greater<double> > q;
        set<int> s;
        s.insert(1);
        vector<int> mask({2,3,5});
        double answer=1;
        for (int i=1;i<n;++i)
        {
            for (int &j:mask)
                if (s.count(answer*j)==0)
                {
                    q.push(answer*j);
                    s.insert(answer*j);
                }
            answer=q.top();
            q.pop();
        }
        return answer;
    }
};

3.动态规划(三指针)

我们先模拟手写丑数的过程
1打头,1乘2 1乘3 1乘5,现在是{1,2,3,5}
轮到2,2乘2 2乘3 2乘5,现在是{1,2,3,4,5,6,10}
手写的过程和采用小顶堆的方法很像,但是怎么做到提前排序呢

小顶堆的方法是先存再排,dp的方法则是先排再存
我们设3个指针p_2,p_3,p_5
代表的是第几个数的2倍、第几个数3倍、第几个数5倍
动态方程dp[i]=min(dp[p_2]2,dp[p_3]3,dp[p_5]*5)
小顶堆是一个元素出来然后存3个元素
动态规划则是标识3个元素,通过比较他们的2倍、3倍、5倍的大小,来一个一个存

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n);
        dp.at(0)=1;
        int p_2,p_3,p_5;
        p_2=p_3=p_5=0;
        for (int i=1;i<n;++i)
        {
            dp.at(i)=min(min(2*dp.at(p_2),3*dp.at(p_3)),5*dp.at(p_5));
            if (dp.at(i)==2*dp.at(p_2))
                ++p_2;
            if (dp.at(i)==3*dp.at(p_3))
                ++p_3;
            if (dp.at(i)==5*dp.at(p_5))
                ++p_5;
        }
        return dp.at(n-1);
    }
};

Java

    public int nthUglyNumber(int n) {
        int[] dp = new int[n];
        dp[0] = 1;
        int i2 = 0, i3 = 0, i5 = 0;
        for (int i = 1; i < n; i++) {
            int min = Math.min(dp[i2] * 2, Math.min(dp[i3] * 3, dp[i5] * 5));
            if (min == dp[i2] * 2) i2++;
            if (min == dp[i3] * 3) i3++;
            if (min == dp[i5] * 5) i5++;
            dp[i] = min;
        }

        return dp[n - 1];
    }

C++

这道题是之前那道 Ugly Number 的拓展,这里让找到第n个丑陋数,还好题目中给了很多提示,基本上相当于告诉我们解法了,根据提示中的信息,丑陋数序列可以拆分为下面3个子列表:

(1) 1x2, 2x2, 2x2, 3x2, 3x2, 4x2, 5x2...

(2) 1x3, 1x3, 2x3, 2x3, 2x3, 3x3, 3x3...

(3) 1x5, 1x5, 1x5, 1x5, 2x5, 2x5, 2x5...

仔细观察上述三个列表,可以发现每个子列表都是一个丑陋数分别乘以 2,3,5,而要求的丑陋数就是从已经生成的序列中取出来的,每次都从三个列表中取出当前最小的那个加入序列,请参见代码如下:

解法一:

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> res(1, 1);
        int i2 = 0, i3 = 0, i5 = 0;
        while (res.size() < n) {
            int m2 = res[i2] * 2, m3 = res[i3] * 3, m5 = res[i5] * 5;
            int mn = min(m2, min(m3, m5));
            if (mn == m2) ++i2;
            if (mn == m3) ++i3;
            if (mn == m5) ++i5;
            res.push_back(mn);
        }
        return res.back();
    }
};

我们也可以使用最小堆来做,首先放进去一个1,然后从1遍历到n,每次取出堆顶元素,为了确保没有重复数字,进行一次 while 循环,将此时和堆顶元素相同的都取出来,然后分别将这个取出的数字乘以 2,3,5,并分别加入最小堆。这样最终 for 循环退出后,堆顶元素就是所求的第n个丑陋数,参见代码如下:

解法二:

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue<long, vector<long>, greater<long>> pq;
        pq.push(1);
        for (long i = 1; i < n; ++i) {
            long t = pq.top(); pq.pop();
            while (!pq.empty() && pq.top() == t) {
                t = pq.top(); pq.pop();
            }
            pq.push(t * 2);
            pq.push(t * 3);
            pq.push(t * 5);
        }
        return pq.top();
    }
};

以上是关于Leetcode丑数序列的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode(算法)- 264. 丑数 II

⭐算法入门⭐《二分枚举》中等05 —— LeetCode 1201. 丑数 III

leetcode-Ugly Number(丑数)

LeetCode——264. 丑数 II

Leetcode 264.丑数II

LeetCode--263--丑数