並べ替えられていない整数のリストに存在しない(リストの最小値より大きい)最初の(最小の)整数を見つける最も速い方法は何ですか?
私の基本的なアプローチは、それらをソートしてリストをステップすることです、より良い方法はありますか?
If you have a question about… •algorithm and data structure concepts
それはトピックIMHOにあります。
並べ替えられていない整数のリストに存在しない(リストの最小値より大きい)最初の(最小の)整数を見つける最も速い方法は何ですか?
私の基本的なアプローチは、それらをソートしてリストをステップすることです、より良い方法はありますか?
If you have a question about… •algorithm and data structure concepts
それはトピックIMHOにあります。
回答:
「数値」と言うときに「整数」を意味すると仮定すると、サイズ2 ^ nのビットベクトルを使用できます。nは要素の数です(範囲に1〜256の整数が含まれている場合、256-ビット、または32バイト、ビットベクトル)。範囲の位置nの整数に出会ったとき、n番目のビットを設定します。
整数のコレクションの列挙が完了したら、ビットセット内のビットを反復処理して、ビットセット0の位置を探します。これらは、欠落している整数の位置nと一致します。
これはO(2 * N)であるため、O(N)であり、おそらくリスト全体をソートするよりもメモリ効率が高くなります。
難解で「賢い」ために、「ホール」が1つしかないアレイの特殊なケースでは、XORベースのソリューションを試すことができます。
これは、ビットベクトルソリューションと同様に約2Nの時間で実行されますが、N> sizeof(int)に対して必要なメモリスペースは少なくなります。ただし、配列に複数の「穴」がある場合、Xはすべての穴のXOR「合計」になり、実際の穴の値に分けることは困難または不可能になります。その場合、他の答えから「ピボット」または「ビットベクトル」アプローチなどの他の方法にフォールバックします。
ピボット方式に似たものを使用して、これを再帰して、複雑さをさらに軽減することもできます。ピボットポイントに基づいて配列を再配置します(これは左側の最大値と右側の最小値になります。ピボット中に完全な配列の最大値と最小値を見つけるのは簡単です)。ピボットの左側に1つ以上の穴がある場合は、その側のみに再帰します。そうでなければ、反対側に再帰します。穴が1つしかないと判断できる任意の時点で、XORメソッドを使用して見つけます(これは、既知の穴を持つ2つの要素のコレクションにピボットし続けるよりも全体的に安価である必要があります。純粋なピボットアルゴリズム)。
いいえ、そうでもありません。まだスキャンされていない番号は、常に特定の「穴」を満たす番号になる可能性があるため、各番号を少なくとも1回スキャンしてから、可能な番号と比較することは避けられません。おそらくバイナリツリーなどを構築し、左から右に穴が見つかるまでそれを走査することで速度を上げることができますが、それは並べ替えと本質的に同じ時間の複雑さです。そして、おそらくあなたはティムソートよりも速いものを思い付かないでしょう。
ここでのほとんどのアイデアは、単なる並べ替えに過ぎません。ビットベクトルバージョンは、プレーンバケットソートです。ヒープの並べ替えについても言及しました。基本的には、時間/空間の要件に加えて、要素の範囲と数にも依存する適切な並べ替えアルゴリズムを選択することに要約されます。
私の見解では、ヒープ構造を使用することがおそらく最も一般的なソリューションです(基本的に、ヒープは完全なソートなしで最小の要素を効率的に提供します)。
また、最初に最小数を見つけてからそれより大きい整数をスキャンするアプローチを分析することもできます。または、ギャップが生じることを期待して、5つの最小数を見つけます。
これらのアルゴリズムはすべて、入力特性とプログラム要件に応じて強度があります。
追加のストレージを使用しない、または整数の幅(32ビット)を想定しないソリューション。
1つの線形パスで最小数を見つけます。これを「最小」と呼びましょう。O(n)時間の複雑さ。
ランダムなピボット要素を選択し、クイックソートスタイルのパーティションを作成します。
ピボットが=( "pivot"-"min")の位置になった場合、パーティションの右側で再帰し、そうでない場合はパーティションの左側で再帰します。ここでの考え方は、最初から穴がない場合、ピボットは(「ピボット」-「最小」)番目の位置にあるため、最初の穴はパーティションの右側に、その逆も同様であるということです。
基本ケースは1つの要素の配列であり、この要素と次の要素の間に穴があります。
予想される総実行時間の複雑さはO(n)(定数を使用した8 * n)であり、最悪の場合はO(n ^ 2)です。同様の問題の時間複雑性分析はここで見つけることができます。
重複がないことが保証されている場合、一般的かつ効率的に動作するものを考え出したと思います*(ただし、任意の数のホールと任意の範囲の整数に拡張可能である必要があります)。
この方法の背後にある考え方は、クイックソートに似ています。つまり、ピボットとその周りのパーティションを見つけ、穴のある側で再帰します。どの側面に穴があるかを調べるために、最小値と最大値を見つけ、それらをその側面のピボットと値の数と比較します。ピボットが17で、最小数が11であるとします。穴がない場合は、6つの数(11、12、13、14、15、16、17)が必要です。5個ある場合、その側に穴があることがわかり、その側だけを再帰して見つけることができます。それよりも明確に説明できないので、例を見てみましょう。
15 21 10 13 18 16 22 23 24 20 17 11 25 12 14
ピボット:
10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25
15は、パイプ(||
)で示されるピボットです。ピボットの左側に5つの番号があり(15-10)、右側に9があります(10-25-15)。したがって、右側を再帰します。穴が隣接している場合、前の境界は15であったことに注意してください(16)。
[15] 18 16 17 20 |21| 22 23 24 25
左側に4つの数字がありますが、5(21-16)あるはずです。そのため、ここで再帰し、前の境界(括弧内)に再び注目します。
[15] 16 17 |18| 20 [21]
左側には正しい2つの番号(18-16)がありますが、右側には2ではなく1(20-18)があります。終了条件に応じて、1の数値を2つの側(18、20)と比較して、19が欠落しているか、もう一度再帰していることを確認できます。
[18] |20| [21]
左側のサイズはゼロで、ピボット(20)と前の境界(18)の間にギャップがあるため、19が穴です。
*:重複がある場合、おそらくハッシュセットを使用してO(N)時間でそれらを削除し、メソッド全体をO(N)のままにしておくことができますが、他のメソッドを使用するよりも時間がかかる場合があります。