可能性のある複製:
2つの並べ替えられた配列の和集合でk番目に小さい要素を見つける方法は?
これは私の友人の一人がインタビュー中に尋ねられた質問で、私は解決策について考えていました。
準線形時間は私にとって対数を意味するので、おそらくある種の分割統治法です。簡単にするために、両方の配列が同じサイズであり、すべての要素が一意であるとしましょう
これは、部分配列_A[0..n-1]
_と_B[0..n-1]
_の2つの同時バイナリ検索であると思います。これはO(log n)です。
A
にある場合は_A[n-1]
_の前またはどこかに表示されます。配列B
にある場合は_B[n-1]
_です。a
のインデックスA
のアイテムと、b
のインデックスB
のアイテムを検討してください。a + b > n
_の場合、検索セットを減らしますA[a] > B[b]
_ then _b = b / 2
_、else _a = a / 2
_a + b < n
_の場合、検索セットを増やしますA[a] > B[b]
_ then _b = 3/2 * b
_、else _a = 3/2 * a
_(a
と前のa
の中間)a + b = n
_の場合、nth最大はmax(A[a], B[b])
です私は最悪の場合O(ln n)を信じていますが、いずれにしても確実に準線形です。
バイナリ検索のバリアントを使用してこの問題を解決できると思います。このアルゴリズムの直感は次のとおりです。 2つの配列をAとBとし、簡単にするために、それらは同じサイズであると仮定します(これからわかるように、これは必要ありません)。各配列について、並列配列AcおよびBcを作成できます。各インデックスiについて、Ac [i]は2つの配列の要素数で、A [i]以下で、Bc [i]はB [i]以下の2つの配列の要素。これらの配列を効率的に構築できれば、AcとBcの両方でバイナリ検索を実行して値kを見つけることにより、k番目に小さい要素を効率的に見つけることができます。そのエントリに対応するAまたはBの対応するエントリは、k番目に大きい要素です。 AcとBcの2つの配列がソートされているため、バイナリ検索は有効です。これは、かなり簡単に理解できると思います。
もちろん、このソリューションは亜線形時間では機能しません。これは、配列AcとBcを作成するのにO(n)時間かかるためです。問題は、次のとおりです。 暗黙的にこれらの配列を作成しますか?つまり、必ずしも各要素を作成せずに、これらの配列の値を決定できますか?I thinkこのアルゴリズムを使用して、答えは「はい」です。配列Aを検索して、k番目に最小の値があるかどうかを確認します。k番目に小さい値は、位置Aの後の配列Aの配列には表示されないことを知っています(すべての要素が異なると仮定します)。配列Aの最初のk要素。これらの値を次のようにバイナリ検索します。位置k/2から開始します。これは、配列Aのk/2番目に小さい要素です。次に、配列Bでバイナリ検索を実行して、この値よりも小さいBの最大値を見つけて、配列内のその位置を確認します。これは、現在の値よりも小さいBの要素の数です。poを合計するとAとBの要素の位置では、2つの配列の要素の総数が現在の要素よりも少なくなっています。これが正確にkであれば、完了です。これがkより小さい場合、Aの最初のk要素の上半分で再帰します。これがkより大きい場合、kの最初の要素の下半分で再帰します。最終的には、次のいずれかになります。 k番目に大きい要素が配列Aにあることを確認します。この場合は完了です。それ以外の場合は、アレイBでこのプロセスを繰り返します。
このアルゴリズムの実行時間は次のとおりです。配列Aの検索では、k要素のバイナリ検索が行われ、O(lg k)回の反復が行われます。 Bでバイナリ検索を行う必要があるため、各反復のコストはO(lg n)です。これは、この検索の合計時間がO(lg k lg n)であることを意味します。配列Bでこれを行う時間は同じであるため、アルゴリズムの正味ランタイムはO(lg k lg n)= O(lg2 n)= o(n)、これはサブリニアです。
これはカークの答えとよく似ています。
Find( nth, A, B )
をn番目の数値を返す関数とし、| A | + | B | > = n。これは、配列のいずれかが3要素未満であるかどうかを確認しない単純な疑似コードです。小さな配列の場合は、大きな配列で1つまたは2つのバイナリ検索を行うだけで、必要な要素を見つけることができます。
_Find( nth, A, B )
If A.last() <= B.first():
return B[nth - A.size()]
If B.last() <= A.first():
return A[nth - B.size()]
Let a and b indexes of middle elements of A and B
Assume that A[a] <= B[b] (if not swap arrays)
if nth <= a + b:
return Find( nth, A, B.first_half(b) )
return Find( nth - a, A.second_half(a), B )
_
これはlog(|A|) + log(|B|)
であり、入力配列はそれぞれn個の要素を持つことができるため、log(n)
複雑になります。
int[] a = new int[] { 11, 9, 7, 5, 3 };
int[] b = new int[] { 12, 10, 8, 6, 4 };
int n = 7;
int result = 0;
if (n > (a.Length + b.Length))
throw new Exception("n is greater than a.Length + b.Length");
else if (n < (a.Length + b.Length) / 2)
{
int ai = 0;
int bi = 0;
for (int i = n; i > 0; i--)
{
// find the highest from a or b
if (ai < a.Length)
{
if (bi < b.Length)
{
if (a[ai] > b[bi])
{
result = a[ai];
ai++;
}
else
{
result = b[bi];
bi++;
}
}
else
{
result = a[ai];
ai++;
}
}
else
{
if (bi < b.Length)
{
result = b[bi];
bi++;
}
else
{
// error, n is greater than a.Length + b.Length
}
}
}
}
else
{
// go in reverse
int ai = a.Length - 1;
int bi = b.Length - 1;
for (int i = a.Length + b.Length - n; i >= 0; i--)
{
// find the lowset from a or b
if (ai >= 0)
{
if (bi >= 0)
{
if (a[ai] < b[bi])
{
result = a[ai];
ai--;
}
else
{
result = b[bi];
bi--;
}
}
else
{
result = a[ai];
ai--;
}
}
else
{
if (bi >= 0)
{
result = b[bi];
bi--;
}
else
{
// error, n is greater than a.Length + b.Length
}
}
}
}
Console.WriteLine("{0} th highest = {1}", n, result);