▶ 三个与丑数相关的问题
▶ 第 263题,判定一个数字是否是丑数,即其素因子是否仅由 2,3,5 构成。
● 常规消除判别,4 ms
1 class Solution 2 { 3 public: 4 bool isUgly(int num) 5 { 6 if (num < 1) 7 return false; 8 for (; !(num % 2); num /= 2); 9 for (; !(num % 3); num /= 3); 10 for (; !(num % 5); num /= 5); 11 return num == 1; 12 } 13 };
● 递归方法,6 ms
1 class Solution 2 { 3 public: 4 bool isUgly(int num) 5 { 6 if (num < 1) 7 return false; 8 return judge(num); 9 } 10 bool judge(int n) 11 { 12 if (n < 7) 13 return true; 14 if (!(n % 2)) 15 return isUgly(n / 2); 16 else if (!(n % 3)) 17 return isUgly(n / 3); 18 else if (!(n % 5)) 19 return isUgly(n / 5); 20 return false; 21 } 22 };
▶ 第 264 题,计算第 n 个丑数。第 263 题就是个坑,不能用逐个判断的方法来找,否则超时
● 用第 263 题的方法逐个检查,计算 1352 时超时,使用递归法逐个检查也是计算 1352 时超时。
1 class Solution 2 { 3 public: 4 bool isUgly(int num) 5 { 6 if (num < 1) 7 return false; 8 for (; !(num % 2); num /= 2); 9 for (; !(num % 3); num /= 3); 10 for (; !(num % 5); num /= 5); 11 return num == 1; 12 } 13 int nthUglyNumber(int n) 14 { 15 int i, count; 16 for (i = 1, count = 0; count < n; i++) 17 { 18 if (isUgly(i)) 19 count++; 20 } 21 return i - 1; 22 } 23 };
● 筛法,空间开销爆炸,丑数的增长速度远小于自然数的增长速度
1 class Solution 2 { 3 public: 4 int nthUglyNumber(int n) 5 { 6 if (n < 7) 7 return n; 8 vector<bool>table(n * 2, false); 9 int i, count; 10 for (i = 1; i < 7; table[i++] = true);// 1 ~ 6 都是丑数,table[0] 闲置 11 for (; i < n * 2; i++) 12 table[i] = !(i % 2) && table[i / 2] || !(i % 3) && table[i / 3] || !(i % 5) && table[i / 5]; 13 for (i = 7,count = 6; i < n * 2 && count < n; i++) 14 { 15 if (table[i]) 16 count++; 17 } 18 return i - 1; 19 } 20 };
● 代码,8 ms,正解,每次从已经有的丑数中选出三个来分别乘以 2,乘以 3,乘以 5,谁最小就收录为新的丑数,同时选取的指针向大端移动一格
1 class Solution 2 { 3 public: 4 int nthUglyNumber(int n) 5 { 6 if (n < 7) 7 return n; 8 vector<int> table(n); 9 int i, t2, t3, t5; 10 for (i = table[0] = 1, t2 = t3 = t5 = 0; i < n; i++) 11 { 12 table[i] = min(table[t2] * 2, min(table[t3] * 3, table[t5] * 5)); 13 if (table[i] == table[t2] * 2) 14 t2++; 15 if (table[i] == table[t3] * 3) 16 t3++; 17 if (table[i] == table[t5] * 5) 18 t5++; 19 } 20 return table[n - 1]; 21 } 22 };
● 代码,41 ms,使用优先队列,每次将出队的丑数的 2 倍,3 倍,5 倍分别入队,排在后边备选
1 class Solution 2 { 3 public: 4 int nthUglyNumber(int n) 5 { 6 if (n == 1) 7 return 1; 8 priority_queue<int, vector<int>, greater<int>> q; 9 int i, temp; 10 for (i = 1, q.push(1); i < n; i++) 11 { 12 for (temp = q.top(), q.pop(); !q.empty() && q.top() == temp; q.pop());// 删除队头等值重复 13 if (temp <= 0x7fffffff / 2) 14 q.push(temp * 2); 15 if (temp <= 0x7fffffff / 3) 16 q.push(temp * 3); 17 if (temp <= 0x7fffffff / 5) 18 q.push(temp * 5); 19 } 20 return q.top(); 21 } 22 };
● 代码,计算不超过一个给定的正整数的丑数的个数,再对所求第 n 个丑数进行二分搜索,计算 1600 时超时。
■ 重要的点:
1 class Solution 2 { 3 public: 4 int nums5(int val)// 计算不超过 val 的正整数中形如 5^p 的数字个数 5 { 6 int n = 0; 7 for (n = 0; val >= 5; n++, val /= 5); 8 return n; 9 } 10 int nums35(int val)// 计算不超过 val 的正整数中形如 3^q × 5^p 的数字个数 11 { 12 int n; 13 for (n = 0; val >= 3; n += 1 + nums5(val), val /= 3); 14 // 第 k 次循环时向 n 增添 3^(k-1) (一个)及形如 3^(k-1) × 5^p 的数字个数(等价于 val / 3^(k-1) 中形如 5^p 的数字个数) 15 return n; 16 } 17 int nums235(int val)// 计算不超过 val 的正整数中形如 2^r × 3^q × 5^p 的数字个数(丑数个数) 18 { 19 int n; 20 for (n = 0; val >= 2; n += 1 + nums35(val), val /= 2); 21 // 第 k 次循环时向 n 增添 2^(k-1) (一个)及形如 2^(k-1) × 3^q × 5^p 的数字个数(等价于 val / 2^(k-1) 中形如 3^q × 5^p 的数字个数) 22 return n; 23 } 24 int nthUglyNumber(int n)//用二分法查找第 n 个丑数,若 ≤ x 的丑数个数是 n,而 ≤ x - 1 的丑数个数是 n - 1,则 x 就是第 n 个丑数 25 { 26 if (n < 7) 27 return n; 28 n--; // 之后的算法中认为 2 是第一个丑数 29 int valLp, numLp, valRp, numRp, valMp, numMp; 30 for (valLp = 1, valRp = 2, numLp = 0, numRp = 1; numRp < n; valLp = valRp, numLp = numRp, valRp = valLp * 2, numRp = nums235(valRp)); 31 // 不断扩大扩大搜索范围,使得在 [ valLp, valRp ] 范围内有 [ numLp, numRp ] 个丑数,valRp = 2 × valLp,numLp ≤ n ≤ numRp 32 if (numRp == n) // 恰好碰到了上限 33 return valRp; 34 for(valMp = (valLp + valRp) / 2;; valMp = (valLp + valRp) / 2) 35 { 36 numMp = nums235(valMp); 37 if (valRp == valMp + 1 && numMp == n - 1 && numRp == n)// val 和 num 在 Mp 和 Rp 之间都有跳跃,Rp 即为所求 38 return valRp; 39 if (valMp == valLp + 1 && numLp == n - 1 && numMp == n)// val 和 num 在 Lp 和 Mp 之间都有跳跃,Mp 即为所求 40 return valMp; 41 if (numMp >= n) // Mp 偏大 42 valRp = valMp, numRp = numMp; 43 else // Mp 偏小 44 valLp = valMp, numLp = numMp; 45 } 46 } 47 };
▶ 第 313 题,扩展丑数,将上述限制条件 “素因子仅由 2,3,5 构成” 改为素因子由一个数组指定,仍然要求第 n 个丑数。
● 代码,31 ms,将上述选最小的方法进行扩展
1 class Solution 2 { 3 public: 4 int nthSuperUglyNumber(int n, vector<int>& primes) 5 { 6 const int m = primes.size(); 7 vector<int> table(n), count(m, 0); 8 int i, j, minValue; 9 for (i = table[0] = 1; i < n; table[i++] = minValue) 10 { 11 for (j = 0, minValue = 0x7fffffff; j < m; j++) 12 { 13 if (minValue > primes[j] * table[count[j]]) 14 minValue = primes[j] * table[count[j]]; 15 } 16 for (j = 0; j < m; j++) 17 { 18 if (minValue == primes[j] * table[count[j]]) 19 count[j]++; 20 } 21 } 22 return table[n - 1]; 23 } 24 };
● 大佬的代码,20 ms,基本相同的算法,优化了循环过程
1 class Solution 2 { 3 public: 4 int nthSuperUglyNumber(int n, vector<int>& primes) 5 { 6 vector<int> ugly(n, 0), ind(primes.size(), 0), val(primes.size(), 1); 7 int i, j, next; 8 for (i = 1, ugly[0] = 1; i < n; ugly[i++] = next) 9 { 10 for (j = 0, next = INT_MAX; j < primes.size(); j++) 11 { 12 if (val[j] == ugly[i - 1]) 13 val[j] = ugly[ind[j]++] * primes[j]; 14 next = next < val[j] ? next : val[j]; 15 } 16 } 17 return ugly[n - 1]; 18 } 19 };