記事ディレクトリ
263. 醜い数字
アイデア
- まず第一に、醜い数値は正の整数である必要があるため、
n<1
; に対して直接 false を返すことができます。 - の場合
n >= 1
、n が 2/3/5 で割り切れる場合、それはそれらが醜い数であることを意味します。
コード
class Solution {
public:
bool isUgly(int n) {
// ugly只能是正整数
if(n < 1) return false;
vector<int> factors = {
2, 3, 5};
for(int i=0; i<=2; ++i){
while(n % factors[i] == 0){
n /= factors[i];
}
}
return n == 1;
}
};
264. 丑数 II
方法 1: 最小ヒープ
アイデア
- n 番目の醜い数値を取得するには、 min-heap実装を使用できます。
- 初期化ヒープは空であり、最小の醜い数値 1 が最初に追加されます。ヒープの最上位要素 x が取り出されるたびに、x はヒープ内の最小の醜い数値になります。2x、3x、5x も醜い数値である必要があるため、これらも最小ヒープに追加されます。
- ただし、上記のアプローチでは要素の重複が発生するため、この状況を回避するには、ハッシュ セットを使用して重複を削除し、同じ要素をヒープに複数回追加しないようにします。
- 重複要素を除外した場合、最小ヒープから取り出したn番目の要素がn番目の醜い番号となります。
コード
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> factor = {
2, 3, 5};
priority_queue<long, vector<long>, greater<long>> heap;
unordered_set<long> s;
// 先把丑数 1 加入
s.insert(1L);
heap.push(1L);
long cur;
for(int i=0; i<n; ++i){
cur = heap.top();
heap.pop();
for(int f : factor){
// 依次计算 2x 3x 5x
long temp = cur * f;
// s中没有temp 将其存入
if(!s.count(temp)){
s.insert(temp);
heap.push(temp);
}
}
}
return (int)cur;
}
};
方法 2: 動的プログラミング (3 ポインタ法)
アイデア
-
方法 1 では、最小ヒープを使用します。これにより、事前にさらに多くの醜い数値が格納されます。また、最小ヒープを維持するプロセスにより、時間の複雑さが高まります。動的プログラミング手法を最適化に使用できます。
-
配列 dp を定義します。ここで dp[i] は i 番目の醜い数値を表し、dp[n] がこの質問の答えになります。このうち dp[1] = 1 となります。
-
残りの醜い数値は、3 つのポインター p 2、p 3、および p 5を通じて計算できます。p iの意味は、i を掛ける資格のある最もピエロな数字の位置です。ここでの修飾とは次のことを指します: 醜い数値 nums[pi ]に i を掛けて次の醜い数値を得ることができる場合、この醜い数値 nums[pi ]は決してiを掛けることができません。 +、nums[pi ]が次の醜い数値を指すようにしてください。
-
例えば:
最初は醜い数は {1} だけです。1 は 2、3、5 倍することができ、最小の 1×2=2 が醜い数列に追加されます。
醜い数字の中に {1, 2} があります。前のステップで 1 に 2 を掛けています。したがって、今後 1×2 を比較する必要はありません。そのため、1 は資格を失ったと考えられます。 2を掛けます。
ここで、1 は 3 と 5 を掛けることができ、2 は 2、3、5 を掛けることができますが、2×3 と 2×5 は明らかに 1×3 と 1×5 より大きいので、比較する必要があります。したがって、 1×3、1×5、2×2を比較するだけで済みます。
類推により、毎回 2、3、5 と乗算できる最も醜い数値を比較し、最小のものを次の醜い数値として選択します。選択された醜い数値は i と同じであると仮定します (i=2) , 3, 5) なので、i と乗算する資格が失われます。対応する p i ++ を置き、piが次の醜い数値を指すようにします。
コード
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 = 2 * dp[p2];
int num3 = 3 * dp[p3];
int num5 = 5 * dp[p5];
dp[i] = min(min(num2, num3), num5);
// 确定dp[i]是由哪个数字生成的
if(dp[i] == num2) p2++;
if(dp[i] == num3) p3++;
if(dp[i] == num5) p5++;
}
return dp[n];
}
};
1201. 醜い数字Ⅲ
手法:二分探索+包含排他原理
アイデア
-
まず注意すべきことは、この質問における「醜い数字」の定義が前の 2 つの質問とは異なるということです。醜い数値 x をここで直接列挙することはできません (3 ポインター法)。x は大きすぎてタイムアウトが発生するためです。
-
この質問は878. N 番目の魔法の数字のアップグレード版です。より難しくするには、2 つの数字を 3 つの数字に変更します。質問 878 と比較すると、この質問は x 以下の醜い数の数を求める関数を変更するだけで済み、二分探索部分はまったく同じです。最初に質問 878 を確認することをお勧めします。この質問の思考マップを以下に添付します。
-
∪B∪C∣ 以下の a の倍数、b の倍数、および c の倍数で構成される集合は、包含排他原理から取得できます。
∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣
-
したがって、x 以下の醜い数値の数は次のようになります
x/a + x/b + x/c - x/lcm_a_b - x/lcm_b_c - x/lcm_a_c + x/lcm_a_b_c
。最小公倍数関数を使用できますstd::lcm
。 -
次に、二分探索によって、特定の位置に対応する数値に正確に n 個の醜い数値因数が含まれるまで、境界が継続的に縮小されます。
コード
class Solution {
public:
using ll = long long;
ll nthUglyNumber(ll n, ll a, ll b, ll c) {
ll lcm_a_b = std::lcm(a, b), lcm_a_c = std::lcm(a, c), lcm_b_c = std::lcm(b, c);
ll lcm_a_b_c= std::lcm(lcm_a_b, c);
// 最小的丑数必然是a、b、c的最小值
ll left = min(min(a, b), c);
ll right = n * left;
while(left + 1 < right){
ll mid = (left + right) / 2;
if(mid/a + mid/b + mid/c - mid/lcm_a_b - mid/lcm_a_c - mid/lcm_b_c + mid/lcm_a_b_c < n){
left = mid;
}
else right = mid;
}
return right;
}
};
313.超醜い数字
方法: 「複数方向のマージ」
アイデア
-
この質問は実際には
264. 丑数 II
の発展型です。前者は 2、3、5 にそれぞれ対応する 3 つのポインタを使用します。ただし、この質問の素数配列の長さは固定されていないため、ポインタ配列を使用して各値に対応させます。素数の。 -
最初の醜い数値は 1 である必要があり、「将来生成される醜い数値」はすべて「既存の醜い数値」に基づいています (「既存の醜い数値」に「指定された素因数」を乗じた primes[i] を使用します)。具体的なプロセスを図に示します。
- 明らかに、毎回最小値を取得し、ポインタを後方に移動し (次の醜い数値を指します)、n 番目の醜い数値が見つかるまでこのプロセスを繰り返す必要があります。
- さらに、各ポインターの動きと dp の構造は単調増加するため、 Set 構造体を参照せずにdp[i-1] と比較することで重複排除を実現できます。
コード
class Solution {
public:
long nthSuperUglyNumber(int n, vector<int>& primes) {
int len = primes.size();
// 指针数组
vector<long> ptr(len, 1);
vector<long> dp(n+1, 0);
dp[1] = 1;
for(int i=2;i<=n;++i){
int flag = 0;
dp[i] = primes[0] * dp[ptr[0]];
for(int j=0; j<len; ++j){
long cur = primes[j] * dp[ptr[j]];
if(cur < dp[i]){
flag = j;
dp[i]= cur;
}
}
ptr[flag]++;
// 如果当前值和上一个丑数一样,那么跳过该丑数
if(dp[i] == dp[i-1]) i--;
}
return dp[n];
}
};
要約する
- 上記はすべて醜い数字に関する質問ですが、醜い数字の定義は固定されていないため、計算を始める前に質問の意味をよく理解する必要があります。
- というのは、単純な数学的アイデア
263. 丑数
を使用して解決できるからです。 - の場合、最小ヒープと動的プログラミング(つまり、3 ポインター
264. 丑数II
法)が使用されますが、最小ヒープ法は時間がかかるため、2 番目の方法の方が推奨されます。 - については
1201. 丑数III
、醜い数の一般的な定義とは異なり、878 のアップグレード バージョンです。N 番目の魔法の数。二分探索と包含-排他原則を使用します。 - の場合
313.超级丑数
、 は のアップグレード バージョンであり、264. 丑数II
3 ポインタ メソッドを複数のポインタに拡張することで解決できます。これは、マルチウェイ マージ メソッドとしても理解できます。