天天看點

264. 醜數 II題目描述方法一:暴力法方法二:最小堆方法三:動态規劃

264. 醜數 II

文章目錄

  • 題目描述
  • 方法一:暴力法
  • 方法二:最小堆
  • 方法三:動态規劃

題目描述

給你一個整數 n ,請你找出并傳回第 n 個 醜數 。

醜數 就是隻包含質因數 2、3 和/或 5 的正整數。

示例 1:

輸入:n = 10

輸出:12 解釋:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 個醜數組成的序列。

示例 2:

輸入:n = 1

輸出:1

解釋:1 通常被視為醜數。

提示:

1 <= n <= 1690

方法一:暴力法

//連暴力法都沒看懂

//暴力法
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);
    }
};
           
264. 醜數 II題目描述方法一:暴力法方法二:最小堆方法三:動态規劃

方法二:最小堆

要得到從小到大的第 nn 個醜數,可以使用最小堆實作。

初始時堆為空。首先将最小的醜數 11 加入堆。

每次取出堆頂元素 x,則 x 是堆中最小的醜數,由于 2x, 3x, 5x 也是醜數,是以将 2x, 3x, 5x 加入堆。

上述做法會導緻堆中出現重複元素的情況。為了避免重複元素,可以使用哈希集合去重,避免相同元素多次加入堆。

在排除重複元素的情況下,第 n 次從最小堆中取出的元素即為第 n 個醜數。

C++代碼

//既然priority_queue是隊列,那麼先要包含頭檔案#include <queue>
//priority_queue和queue不同的就在于我們可以自定義其中資料的優先級, 讓優先級高的排在隊列前面,優先出隊
class Solution
{
public:
	int nthUglyNumber(int n)
	{
		vector<int> factors = { 2,3,5 };
		unordered_set<long> seen;//unorder版本的map和set隻提供前向疊代器  在一個unordered_set内部,元素不會按任何順序排序,而是通過元素值的hash值将元素分組放置到各個槽(Bucker,也可以譯為“桶”),這樣就能通過元素值快速通路各個對應的元素(均攤耗時為O(1))。
		//priority_queue<Type, Container, Functional>//Functional是仿函數
		priority_queue<long, vector<long>, greater<long>> heap;//greater<long>表明是一個升序隊列,小頂堆。//隊列也是堆
		seen.insert(1L);//堆//1L表明插入的1是long類型的。//1是醜數,提前插入unodered_set中
		heap.push(1L);//堆
		int ugly = 0;
		for (int i = 0; i < n; i++)
		{
			long curr = heap.top();//heap是先進先出,每次取出堆頂元素x,則x是堆中最小的醜數
			heap.pop();
			ugly = (int)curr;//long->int
			for (int factor : factors)
			{
				long next = curr*factor;//有且僅有醜數的 2x,3x,5x 是醜數,是以将 2x,3x,5x 加入堆。
				//unodered_set去除第二次出現的數
				if (!seen.count(next))//unordered_set::count()函數是C++ STL中的内置函數,用于對unordered_set容器中特定元素的出現進行計數。由于unordered_set容器不允許存儲重複的元素,是以該功能通常用于檢查容器中是否存在元素。如果元素存在于容器中,則該函數傳回1,否則傳回0。
				{
					seen.insert(next);
					heap.push(next);//push(const T& obj):将obj的副本放到容器的适當位置,這通常會包含一個排序操作。這也是priority_queue與普通queue的差別。
				}
			}
		}
		return ugly;
	}
};
           

上述代碼的關鍵點有且僅有醜數的 2x,3x,5x 是醜數,是以将醜數的 2x,3x,5x 加入堆(x代表倍數)。

是以由已知的的醜數可以推導出所有醜數,從小到大的醜數的2x,3x,5x必定是醜數,但是這中間會有重複的數,是以要用unordered_set去重。

時間複雜度:O(nlogn)。得到第 n 個醜數需要進行 n 次循環,每次循環都要從最小堆中取出 1 個元素以及向最小堆中加入最多 3 個元素,是以每次循環的時間複雜度是O(logn+log3n)=O(logn),總時間複雜度是 O(nlogn)。

空間複雜度:O(n)。空間複雜度主要取決于最小堆和哈希集合的大小,最小堆和哈希集合的大小都不會超過 3n。

264. 醜數 II題目描述方法一:暴力法方法二:最小堆方法三:動态規劃

方法三:動态規劃

//似懂非懂

方法二使用最小堆,會預先存儲較多的醜數,導緻空間複雜度較高,維護最小堆的過程也導緻時間複雜度較高。可以使用動态規劃的方法進行優化。

264. 醜數 II題目描述方法一:暴力法方法二:最小堆方法三:動态規劃

我們先模拟手寫醜數的過程

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 個指針 p2,p3,p5代表的是第幾個數的2倍、第幾個數 3 倍、第幾個數 5 倍

動态方程 dp[i]=min(dp[p2]*2,dp[p3]*3,dp[p5]*5)

小頂堆是一個元素出來然後存 3 個元素

動态規劃則是辨別 3 個元素,通過比較他們的 2 倍、3 倍、5 倍的大小,來一個一個存

class Solution
{
public:
	int nthUglyNumber(int n)
    {
        vector<int> dp(n + 1);
        dp[1] = 1;
        int p2 = 1, p3 = 1, p5 = 1;
        for (int i = 2; i <= n; i++)
        {
            int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
            dp[i] = min(min(num2, num3), num5);
            if (dp[i] == num2)
            {
                p2++;
            }
            if (dp[i] == num3)
            {
                p3++;
            }
            if (dp[i] == num5)
            {
                p5++;
            }
        }
        return dp[n];
    }
};
           

複雜度分析

時間複雜度:O(n)。需要計算數組 dp 中的 n 個元素,每個元素的計算都可以在 O(1) 的時間内完成。

空間複雜度:O(n)。空間複雜度主要取決于數組 dp 的大小。

264. 醜數 II題目描述方法一:暴力法方法二:最小堆方法三:動态規劃

繼續閱讀