• 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 << '
    ';
            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 << '
    ';
            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 << '
    ';
        	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();
        }
    };
    

  • 相关阅读:
    KVC之-setValue:forKey:方法实现原理与验证
    李洪强iOS开发之iOS社区收集
    跟着百度学PHP[15]-会话控制session的工作机制
    代码审计学习之文件操作漏洞
    中间人攻击——ARP欺骗的原理、实战及防御
    跟着百度学PHP[14]-PDO的预处理语句2
    跟着百度学PHP[14]-PDO的预处理语句1
    跟着百度学PHP[14]-PDO之Mysql的事务处理2
    跟着百度学PHP[14]-PDO之Mysql的事务处理1
    跟着百度学PHP[14]-PDO的错误处理模式&PDO执行SQL
  • 原文地址:https://www.cnblogs.com/wwj99/p/12262866.html
Copyright © 2020-2023  润新知