私はしばらく前に面白い就職面接の経験をしました。質問は本当に簡単に始まった。
Q1:
1
、2
、3
、…、100
という数字が入ったバッグがあります。各番号は1回だけ表示されるので、100個の番号があります。今すぐ1つの番号がバッグからランダムに選択されています。足りない番号を見つけてください。
もちろん、私はこのインタビューの質問を以前に聞いたことがあるので、私はすぐに次の項目に沿って答えました。
A1:そうですね、
1 + 2 + 3 + … + N
の数の合計は(N+1)(N/2)
です( ウィキペディア:算術級数の合計 を参照)。N = 100
の場合、合計は5050
です。したがって、すべての数字がバッグに入っている場合、合計は正確に
5050
になります。 1つの数が欠けているので、合計はこれより少なくなり、違いはその数です。それで我々はその欠けている数をO(N)
時間とO(1)
空間で見つけることができます。
この時点で私はうまくいっていると思いましたが、突然質問は予想外の順番になりました。
Q2:それは正しいのですが、 _ two _ の数が足りない場合はどうしますか。
私は今までにこの変化を見たことがない/聞いた/検討したことがなかったので、私はパニックに陥り、質問に答えることができませんでした。インタビュアーは私の思考過程を知ることを主張したので、期待される製品と比較するか、最初のパスから情報を集めた後に2番目のパスを実行するなどしてより多くの情報を得ることができると述べました。実際に解決策への明確な道を開くのではなく、暗闇の中で。
インタビュアーは、第二の方程式を持つことが本当に問題を解決する一つの方法であると言って私を励まそうとしました。この時点で私はちょっと怒っていて(その答えを前もって知らないため)、これが一般的な(読む:「役に立つ」)プログラミング技法なのか、それとも単にトリック/落とし穴なのか。
インタビュアーの答えは私を驚かせた:あなたは3つの欠けている数を見つけるためにテクニックを一般化することができる。実際、 k 欠損値を見つけるように一般化することができます。
Qk:正確に k の数字がバッグから抜けている場合、どのようにしてそれを効率的に見つけることができますか?
これは数ヶ月前のことで、私はまだこのテクニックが何であるかを理解できませんでした。すべての数字を少なくとも一度はスキャンしなければならないので、明らかにΩ(N)
時間の下限がありますが、インタビュアーは、 _ time _ および _ space _ 複雑な解決手法を主張しました(マイナスO(N)
time input scan)は k not _ n _ で定義されています。
だからここでの質問は簡単です:
O(N)
ビットを使用します。 _ n _ に比例した追加のスペースはありません。繰り返しますが、もちろんO(N)
で入力をスキャンしなければなりませんが、( k not _ n _ で定義された)少量の情報しかキャプチャできません。どういうわけか k 欠損値。
これが Dimitris Andreouのlink の要約です。
I = 1,2、..、kのi乗の合計を覚えておいてください。これは連立方程式を解くことへの問題を減らす
ある1 + a2 + ... + ak = b1
ある12 + a22 + ... + ak2 = b2
...
ある1k + a2k + ... + akk = bk
ニュートンのアイデンティティ を使い、bを知る私 計算することができます
c1 = a1 + a2 + ... ak
c2 = a1ある2 + a1ある3 + ... + ak-1あるk
...
ck = a1ある2 ... ak
多項式を展開すると(x-a1)...(x-a)k)係数は正確にcになります1、...、Ck - を見なさい - Vièteの公式 。すべての多項式因子は一意に(多項式の環は ユークリッド領域 )であるため、これは私 順列まで、一意に決定されます。
これは力を覚えていることが数を回復するのに十分であるという証明を終わります。定数kの場合、これは良い方法です。
ただし、kが変化するときは、cを計算する直接アプローチ1、...、Ck 例えば、 ck 欠けているすべての数の積、大きさn!/(n-k)!です。これを克服するには、Zで 計算を実行します。q field 、ここでqは素数でn <= q <2n - それは によって表される - - Bertrandの仮定 。公式はまだ成り立ち、多項式の因数分解はまだユニークなので、証明を変更する必要はありません。有限体上の因数分解、例えば Berlekamp または Cantor-Zassenhaus によるものも必要です。
定数kの高水準擬似コード
Kを変化させるために、例えばを用いて素数n≦q≦2nを見つける。 Miller-Rabinをクリックし、すべての数を剰余数qを減らしてステップを実行します。
編集:この答えの前のバージョンはZの代わりにそれを述べたqここで、qが素数である場合、特性2の有限体(q = 2 ^(log n))を使用することが可能である。ニュートンの公式はkまでの数による除算を必要とするので、これは当てはまりません。
あなたはMuthukrishnan - データストリームアルゴリズム:パズル1:行方不明の番号を見つけること _の2、3ページを読むことによってそれを見つけるでしょう。 探している一般化を正確に示しています 。おそらくこれがあなたのインタビュアーが読んだことであり、なぜ彼はこれらの質問を投げかけたのでしょう。
今、人々だけがMuthukrishnanの扱いに包含されているか置き換えられている答えを削除し始め、このテキストを見つけやすくするならば。 :)
また、 sdcvvcの 直接関連の回答 も参照してください。これには、擬似コードも含まれています(万歳!これらの複雑な数学の定式化を読む必要はありません)。
Q 2を解くには、数字そのものと、 の二乗 の両方を使います。
それから問題を
k1 + k2 = x
k1^2 + k2^2 = y
x
とy
は、合計が期待値をどれだけ下回っているかです。
代入すると次のようになります。
(x-k2)^2 + k2^2 = y
これを解決して、不足している数を特定できます。
@j_random_hackerが指摘したように、これは O(n) timeとO(1) space で重複を見つけることとよく似ています。私の答えを適応させることもここではうまくいきます。
"bag"がサイズA[]
の1から始まる配列N - k
で表されると仮定すると、O(N)
時間とO(k)
追加スペースでQkを解くことができます。
まず、配列A[]
をk
要素で拡張して、サイズがN
になるようにします。これがO(k)
追加スペースです。その後、次の擬似コードアルゴリズムを実行します。
for i := n - k + 1 to n
A[i] := A[1]
end for
for i := 1 to n - k
while A[A[i]] != A[i]
swap(A[i], A[A[i]])
end while
end for
for i := 1 to n
if A[i] != i then
print i
end if
end for
最初のループはk
の余分なエントリを配列の最初のエントリと同じものに初期化します(これはすでに配列に存在することがわかっている便利な値です - このステップの後、サイズの初期配列に欠けていたエントリN-k
はまだ拡張配列にありません。
2番目のループは、要素x
が少なくとも1回存在する場合、それらのエントリの1つが位置A[x]
になるように拡張配列を並べ替えます。
入れ子になったループはありますが、それでもO(N)
時間内で実行されます。スワップは、A[i] != i
のようなi
がある場合にのみ発生し、各スワップはA[i] == i
のような要素を少なくとも1つ設定します。これは、スワップの総数(つまりwhile
ループ本体の総実行数)が最大でN-1
であることを意味します。
3番目のループは、値i
で占められていない配列i
のインデックスを出力します - これはi
が欠けていたにちがいないことを意味します。
私は4歳の子供にこの問題を解決するよう依頼しました。彼は数字をソートしてから数えた。これにはO(キッチンフロア)のスペースが必要で、ボールがなくても同じように簡単に機能します。
それが最も効率的な解決策であるかどうかはわかりませんが、すべてのエントリをループ処理し、ビットセットを使用してどの数値が設定されているかを確認してから0ビットをテストします。
私は簡単な解決策が好きです - そして私は、それが合計や二乗和などを計算するよりも速いかもしれないと信じています。
数の和に基づく解法の問題は、それらが大きな指数を持つ数を保存して扱うコストを考慮に入れていないということです...実際には、非常に大きなnに対して働くために、大きな数のライブラリが使われるでしょう。これらのアルゴリズムのスペース使用率を分析できます。
SdcvvcとDimitris Andreouのアルゴリズムの時間と空間の複雑さを分析できます。
ストレージ:
l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)
l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`
だからl_j \in \Theta(j log n)
使用総ストレージ:\sum_{j=1}^k l_j \in \Theta(k^2 log n)
使用スペース:a^j
の計算にceil(log_2 j)
時間がかかると仮定すると、合計時間:
t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n) \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)
使用された合計時間:\Theta(kn log n)
この時間と空間が十分であれば、単純な再帰的アルゴリズムを使うことができます。 b!iをバッグのi番目のエントリ、nを削除前の数、kを削除数とします。 Haskellの構文では...
let
-- O(1)
isInRange low high v = (v >= low) && (v <= high)
-- O(n - k)
countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
findMissing l low high krange
-- O(1) if there is nothing to find.
| krange=0 = l
-- O(1) if there is only one possibility.
| low=high = low:l
-- Otherwise total of O(knlog(n)) time
| otherwise =
let
mid = (low + high) `div` 2
klow = countInRange low mid
khigh = krange - klow
in
findMissing (findMissing low mid klow) (mid + 1) high khigh
in
findMising 1 (n - k) k
使用される記憶域:リスト用のO(k)
、スタック用のO(log(n))
:O(k + log(n))
このアルゴリズムはより直感的であり、同じ時間複雑さを持ち、そしてより少ないスペースを使用します。
ちょっと待って。質問が述べられているように、バッグには100個の番号があります。 kがいくら大きくても、ループの最大100-k回の繰り返しでセットを使用してそのセットから数字を削除できるため、問題は一定時間で解決できます。 100は定数です。残りの数字のセットがあなたの答えです。
1からNまでの数の解を一般化すると、Nが定数でないこと以外は何も変わらないので、O(N - k)= O(N)時間になります。たとえば、ビットセットを使用する場合は、O(N) timeでビットを1に設定し、数字を繰り返しながら、ビットを0に設定します(O(Nk)= O(N))そして答えがあります。
私には、インタビュアーがO(k)ではなくO(N)時間の最後のセットの内容を プリントアウト にする方法を尋ねていたようです。時間。明らかに、ビットを設定すると、数字を印刷するかどうかを決定するためにすべてのNビットを反復する必要があります。ただし、セットの実装方法を変更した場合は、k回の繰り返しで数値を出力できます。これは、ハッシュセットと二重リンクリストの両方に格納されるようにオブジェクトに番号を入れることによって行われます。ハッシュセットからオブジェクトを削除すると、リストからも削除されます。答えは現在長さkのリストに残されます。
2(および3)の欠損数の問題を解決するには、 quickselect
を修正します。これは平均してO(n)
内で実行され、分割がインプレースで行われる場合は定数メモリを使用します。
ランダムなピボットp
に関して集合をピボットよりも小さい数を含むパーティションl
とピボットよりも大きい数を含むr
に分割します。
ピボット値を各パーティションのサイズと比較して、2つの欠けている番号がどのパーティションにあるかを判断します(p - 1 - count(l) = count of missing numbers in l
とn - count(r) - p = count of missing numbers in r
)。
a)各パーティションに1つの番号が欠けている場合は、合計の差のアプローチを使用して、欠けている各番号を見つけます。
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
と((p+1) + (p+2) ... + n) - sum(r) = missing #2
b)1つのパーティションに両方の番号が欠けていて、そのパーティションが空の場合、欠けている番号は、どのパーティションに番号が欠けているかによって、(p-1,p-2)
または(p+1,p+2)
のいずれかになります。
1つのパーティションに2つの数字が欠けていても空ではない場合は、そのパーティションに再帰します。
欠けている数が2つしかないため、このアルゴリズムは常に少なくとも1つのパーティションを破棄します。そのため、クイック選択のO(n)
平均時間の複雑さは維持されます。同様に、3つの欠損数の場合、このアルゴリズムでもパスごとに少なくとも1つのパーティションが破棄されます(2つの欠損数と同様に、最大1つのパーティションに複数の欠損数が含まれるため)。しかし、足りない数字が追加されると、パフォーマンスがどの程度低下するのかわかりません。
これはnotin-place partitioningを使用する実装です。したがって、この例はスペース要件を満たしていませんが、アルゴリズムのステップを示しています。
<?php
$list = range(1,100);
unset($list[3]);
unset($list[31]);
findMissing($list,1,100);
function findMissing($list, $min, $max) {
if(empty($list)) {
print_r(range($min, $max));
return;
}
$l = $r = [];
$pivot = array_pop($list);
foreach($list as $number) {
if($number < $pivot) {
$l[] = $number;
}
else {
$r[] = $number;
}
}
if(count($l) == $pivot - $min - 1) {
// only 1 missing number use difference of sums
print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
}
else if(count($l) < $pivot - $min) {
// more than 1 missing number, recurse
findMissing($l, $min, $pivot-1);
}
if(count($r) == $max - $pivot - 1) {
// only 1 missing number use difference of sums
print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
} else if(count($r) < $max - $pivot) {
// mroe than 1 missing number recurse
findMissing($r, $pivot+1, $max);
}
}
これは、巧妙なトリックなしで簡単なkビットの追加ストレージを使用するソリューションです。実行時間O(n)、追加スペースO(k)最初に解決策を読み上げたり、天才になったりせずにこれを解決できることを証明するためだけに:
void puzzle (int* data, int n, bool* extra, int k)
{
// data contains n distinct numbers from 1 to n + k, extra provides
// space for k extra bits.
// Rearrange the array so there are (even) even numbers at the start
// and (odd) odd numbers at the end.
int even = 0, odd = 0;
while (even + odd < n)
{
if (data [even] % 2 == 0) ++even;
else if (data [n - 1 - odd] % 2 == 1) ++odd;
else { int tmp = data [even]; data [even] = data [n - 1 - odd];
data [n - 1 - odd] = tmp; ++even; ++odd; }
}
// Erase the lowest bits of all numbers and set the extra bits to 0.
for (int i = even; i < n; ++i) data [i] -= 1;
for (int i = 0; i < k; ++i) extra [i] = false;
// Set a bit for every number that is present
for (int i = 0; i < n; ++i)
{
int tmp = data [i];
tmp -= (tmp % 2);
if (i >= even) ++tmp;
if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
}
// Print out the missing ones
for (int i = 1; i <= n; ++i)
if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
for (int i = n + 1; i <= n + k; ++i)
if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);
// Restore the lowest bits again.
for (int i = 0; i < n; ++i) {
if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
else { if (data [i] % 2 == 0) data [i] += 1; }
}
}
すべての番号が存在するかどうか確認できますか?もしそうなら、あなたはこれを試すことができます:
S =バッグ内のすべての数字の合計(S <5050)
Z =欠損値の合計5050 - S
欠けている数がx
とy
であるならば:
x = Z - yそして
max(x)= Z - 1
それで、あなたは1
からmax(x)
までの範囲をチェックして、そして数を見つけます
このアルゴリズムは質問1でうまくいく可能性があります。
あるいはもっと良い:
def GetValue(A)
val=0
for i=1 to 100
do
val=val^i
done
for value in A:
do
val=val^value
done
return val
このアルゴリズムは、実際には2つの欠けている数に対して拡張することができます。最初のステップは変わりません。 2つの欠けている番号を指定してGetValueを呼び出すと、結果はa1^a2
になります2つの欠けている番号です。まあ言ってみれば
val = a1^a2
今度はvalからa1とa2をふるいにかけるためにvalの任意のセットビットを取ります。 ith
ビットがvalに設定されているとしましょう。これは、a1とa2がith
ビット位置で異なるパリティを持つことを意味します。今度は元の配列に対してもう1回繰り返しを行い、2つのxor値を保持します。 1番目はi番目のビットが設定されている数字用、もう1つはi番目のビットが設定されていない番号です。これで2つの数字のバケツができました。a1 and a2
は別のバケツの中にあると保証されています。それでは、それぞれのバケツで足りない要素を1つ見つけるために行ったのと同じことを繰り返します。
Q2にとってこれは他のものよりも少し非効率的な解決策ですが、それでもO(N)ランタイムを持ち、O(k)スペースを使います。
アイデアは、元のアルゴリズムを2回実行することです。最初のものであなたは足りない合計数を得ます、それはあなたに足りない数の上限を与えます。この番号をN
と呼びましょう。欠けている2つの数がN
になることを知っているので、最初の数は[1, floor((N-1)/2)]
の間にあることができますが、2番目の数は[floor(N/2)+1,N-1]
の中にあることになります。
したがって、最初の間隔に含まれていないすべての番号を破棄して、すべての番号をもう一度ループします。あるもの、あなたは彼らの合計を追跡します。最後に、不足している2つの数字のうちの1つ、さらに2つ目の数字がわかります。
この方法は一般化でき、入力を1回通過する間に複数の検索が「並列」で実行される可能性があると私は感じていますが、その方法はまだわかっていません。
両方のリストの合計と両方のリストの積があれば、Q2を解くことができます。
(l1がオリジナル、l2が修正リストです)
d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)
算術級数の合計は最初と最後の項の平均のn倍なので、これを最適化することができます。
n = len(l1)
d = (n/2)*(n+1) - sum(l2)
これで、(aとbが削除された数の場合)
a + b = d
a * b = m
だから私たちはに並べ替えることができます:
a = s - b
b * (s - b) = m
そして掛け算:
-b^2 + s*b = m
そして、右側がゼロになるように並べ替えます。
-b^2 + s*b - m = 0
それから二次式で解くことができます。
b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b
サンプルPython 3コード:
from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5
私はsqrt、reduce、sum関数の複雑さを知らないので、この解決策の複雑さを解決することはできません(もし誰かが知っているなら以下にコメントしてください)。
このようなストリーミングアルゴリズムを一般化する一般的な方法があります。その考えは、k
要素を独立した部分問題にうまく「広げる」ために少しのランダム化を使用することです、そこで我々のオリジナルのアルゴリズムは問題を解決します。この手法は、特に、スパース信号の再構築に使用されます。
u = k^2
の配列a
を作成します。h : {1,...,n} -> {1,...,u}
を選択してください。 (/のように 乗算シフト )1, ..., n
の各i
に対してa[h(i)] += i
を増やしますx
について、a[h(x)] -= x
をデクリメントします。欠けている数がすべて異なるバケットにハッシュされている場合、配列のゼロ以外の要素には欠けている数が含まれるようになります。
特定のペアが同じバケットに送信される確率は、ユニバーサルハッシュ関数の定義により1/u
よりも小さくなります。 k^2/2
の組があるので、誤り率はせいぜいk^2/2/u=1/2
です。つまり、少なくとも50%の確率で成功し、u
を増やすと確率が上がります。
このアルゴリズムはk^2 logn
ビットのスペースをとることに注意してください(配列バケットごとにlogn
ビットが必要です)。これは@Dimitris Andreouの答えで必要とされるスペースと一致します(特に多項式分解のスペース要件、偶然ランダム化もされます)。電力の合計の場合、時間k
ではなく、更新ごとに一定の時間があります。
実際には、コメントに記載されているトリックを使用することで、パワーサム法よりもさらに効率的になります。
これは複雑な数式や理論がなくてもできると思います。以下は、インプレースおよびO(2n)時間複雑度ソリューションの提案です。
入力フォームの前提
bagの中の数字の数= n
欠けている数の数= k
バッグの中の数字は、長さnの配列で表されます。
Algo = nの入力配列の長さ
配列に欠けているエントリ(バッグから取り出した数字)は、配列の最初の要素の値に置き換えられます。
例えば。最初のバッグは[2,9,3,7,8,6,4,5,1,10]のように見えます。 4が取り出された場合、4の値は2(配列の最初の要素)になります。したがって、4を取り出した後のバッグは[2,9,3,7,8,6,2,5,1,10]のようになります。
この解決法の鍵は、配列がトラバースされるときにそのINDEXの値を否定することによって訪問番号のINDEXにタグを付けることです。
IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
{
List<int> missingNumbers = new List<int>();
int arrayLength = arrayOfNumbers.Length;
//First Pass
for (int i = 0; i < arrayLength; i++)
{
int index = Math.Abs(arrayOfNumbers[i]) - 1;
if (index > -1)
{
arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
}
}
//Second Pass to get missing numbers
for (int i = 0; i < arrayLength; i++)
{
//If this index is unvisited, means this is a missing number
if (arrayOfNumbers[i] > 0)
{
missingNumbers.Add(i + 1);
}
}
return missingNumbers;
}
あなたはおそらくO(k)が何を意味するのかを明確にする必要があるでしょう。
これは任意のkに対する自明な解です:あなたの数の集合の中の各vについて、2 ^ vの合計を累計してください。最後に、iを1からNまでループします。2 ^ iとのビット単位のANDの合計が0の場合、iは失われます。 (または数値的に、2の整数で除算された合計のフロアが偶数の場合、偶数です。またはsum modulo 2^(i+1)) < 2^i
)
簡単でしょ? O(N)時間、O(1)格納、および任意のkをサポートします。
実際のコンピュータではそれぞれO(N)のスペースを必要とする膨大な数を計算していることを除けば。実際、この解決策はビットベクトルと同じです。
それであなたは巧妙に合計と平方の合計と立方体の合計をv ^ kの合計まで計算することができて、そして結果を引き出すために凝った数学をすることができる。しかし、それらも大きな数であり、それが疑問を投げかけています。 O(1)空間にどのくらい収まりますか。また、必要なサイズの数を合計するのにどのくらいの時間がかかりますか?
とてもいい問題です。私はQkのためにセットの違いを使うことになるでしょう。 Rubyのように、多くのプログラミング言語でもサポートされています。
missing = (1..100).to_a - bag
これはおそらく最も効率的な解決策ではありませんが、このようなタスクに直面した場合に実際に使用するものです(既知の境界、低境界)。数のセットが非常に大きい場合は、もちろんより効率的なアルゴリズムを検討しますが、それまでは単純な解決策で十分です。
さらに別の方法は残差グラフフィルタリングを使用することである。
1から4の番号があり、3が欠落しているとします。バイナリ表現は次のとおりです。
1 = 001b、2 = 010b、3 = 011b、4 = 100b
そして、私は次のようなフローグラフを作成することができます。
1
1 -------------> 1
| |
2 | 1 |
0 ---------> 1 ----------> 0 |
| | |
| 1 1 | |
0 ---------> 0 ----------> 0 |
| |
1 | 1 |
1 ---------> 0 -------------> 1
フローグラフはx個のノードを含み、xはビット数です。そして最大エッジ数は(2 * x)-2です。
したがって、32ビット整数の場合、O(32) spaceまたはO(1) spaceが必要になります。
1、2、4から始まる各数値の容量を削除すると、残差グラフが残ります。
0 ----------> 1 ---------> 1
最後に、次のようにループを実行します。
result = []
for x in range(1,n):
exists_path_in_residual_graph(x)
result.append(x)
結果はresult
になり、欠けていない数字も含まれます(誤検知)。しかし、 k <=(結果のサイズ)<= n k
が足りない要素があるとき。
私は最後に与えられたリストを一通り見て結果が欠けているかどうかをマークします。
そのため、時間の複雑さはO(n)になります。
最後に、単に00
と01
ではなく、ノード11
、10
、0
、1
を取ることで、誤検知の数(および必要なスペース)を減らすことができます。
私はその質問に対して異なるアプローチをとり、彼が解決しようとしているより大きな問題についての詳細についてインタビュアーを精査します。問題とそれを取り巻く要件に応じて、明らかな集合ベースの解決策が正しいこともあれば、リストを生成してそれを介して選択するアプローチはそうでないこともあります。
たとえば、インタビュアーがn
メッセージを送信しようとしていて、返信にならなかったk
を知っている必要があり、n-k
番目の返信が到着してからできるだけ短い時間でそれを知る必要があるかもしれません。また、メッセージチャネルの性質として、フルボアで実行しても、最後の返信が到着してから最終結果が出るまでの時間に影響を与えることなく、メッセージ間で何らかの処理を行うのに十分な時間があるとします。その時間は、送信された各メッセージの識別ファセットをセットに挿入し、対応する各応答が到着したときにそれを削除するのに使用できます。最後の応答が到着したら、行うべき唯一のことはセットから識別子を削除することです。これは典型的な実装ではO(log k+1)
を取ります。その後、その集合にはk
個の欠けている要素のリストが含まれているので追加の処理はありません。
全部がO((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
を実行するので、これは確かに事前に生成された数の袋をバッチ処理するための最速のアプローチではありません。しかし(たとえそれが前もって知られていなくても)k
のどんな値に対してもうまくいきます、そして上の例ではそれは最も重要な間隔を最小にする方法で適用されました。
対称性(数学言語のグループ)の観点から考えることで、ソリューションの動機付けを行うことができます。数字のセットの順序に関係なく、答えは同じでなければなりません。 k
関数を使用して、欠落している要素を特定する場合、どの関数がその特性を持っているかを考える必要があります:対称。関数s_1(x) = x_1 + x_2 + ... + x_n
は対称関数の例ですが、他にも高度な関数があります。特に、elementary symmetric functionsを考慮してください。次数2の基本対称関数はs_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
であり、2つの要素のすべての積の合計です。同様に、次数3以上の基本対称関数についても同様です。それらは明らかに対称です。さらに、これらはすべての対称機能のビルディングブロックであることがわかります。
s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
に注意することで、基本的な対称関数を作成できます。さらに考えれば、s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
などが納得できるので、1回のパスで計算できます。
配列から欠落しているアイテムをどのように確認しますか?多項式(z-x_1)(z-x_2)...(z-x_n)
について考えてください。 0
のいずれかの数字を入力すると、x_i
と評価されます。多項式を展開すると、z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
が得られます。基本対称関数もここに表示されます。これは、根に順列を適用しても多項式は変わらないはずなので、驚くことではありません。
したがって、他の人が述べたように、多項式を作成し、それを因数分解して、セットに含まれていない数字を見つけ出すことができます。
最後に、大きな数値でメモリがオーバーフローすることを心配する場合(n番目の対称多項式は100!
の順序になります)、これらの計算を行うことができますmod p
ここで、p
は、 100.その場合、多項式mod p
を評価し、入力がセット内の数値である場合は再び0
に評価され、入力がaである場合はゼロ以外の値に評価されることがわかります。セットにない番号。ただし、他の人が指摘しているように、k
ではなくN
に依存する時間で多項式から値を取得するには、多項式mod p
を因数分解する必要があります。
これは次のように一般化できると思います。
算術級数と乗算の合計の初期値としてS、Mを示します。
S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n
これを計算するための公式について考える必要がありますが、それはポイントではありません。とにかく、1つの番号が欠けていれば、あなたは既に解決策を提供しました。ただし、2つの数値が足りない場合は、新しい合計と合計の倍数をS1とM1で表します。これは次のようになります。
S1 = S - (a + b)....................(1)
Where a and b are the missing numbers.
M1 = M - (a * b)....................(2)
S1、M1、M、Sを知っているので、上記の式は、欠けている数であるaとbを見つけるために解くことができます。
今不足している3つの数字のために:
S2 = S - ( a + b + c)....................(1)
Where a and b are the missing numbers.
M2 = M - (a * b * c)....................(2)
解くことができる2つの方程式がある一方で、今あなたの未知数は3です。
Bloom Filter を使ってみることができます。バッグの中のそれぞれの番号を花の中に挿入してから、それぞれが見つからないことを報告するまで1-kセット全体を繰り返します。これはすべてのシナリオで答えを見つけることができないかもしれませんが十分によいソリューションであるかもしれません。
ほとんどの場合、 Q1とQ2 を O(log n) とすることができます。
memory chip
がn
の数のtest tubes
の配列で構成されているとします。また、試験管内のx
は、x
_milliliter
で表した薬液です。
私たちのプロセッサがlaser light
だとしましょう。我々がレーザーを照らすとき、それはその長さに対して垂直に全ての管を横切る。それが薬液を通過するたびに、明度は1
だけ減少します。そして、あるミリリットルのマークで光を通過させるのがO(1)
の操作です。
我々が試験管の真ん中で我々のレーザーを照らすならば今度は光度の出力を得る
n/2
より大きくなります。n/2
より小さい少なくとも1つの欠けている数があります。また、明るさが1
または2
だけ減少しているかどうかを確認できます。それが1
だけ減らされている場合、欠けている数字の1つはn/2
より小さく、もう1つの数字はn/2
より大きくなります。それが2
によって減らされるならば、両方の数はn/2
より小さいです。上記のプロセスを何度も繰り返して問題の領域を絞り込むことができます。各ステップで、ドメインを半分にします。そしてついに私たちの結果にたどり着くことができます。
言及する価値のある並列アルゴリズム(それらは面白いので)、
O(log^3 n)
時間内に行うことができます。そして、不足している番号はO(log n)
時間の二分探索によって見つけることができます。n
プロセッサがあれば、各プロセスは入力の1つをチェックして番号を識別するフラグを設定することができます(便利には配列で)。そして次のステップで、各プロセスは各フラグをチェックし、最後にフラグが立てられていない番号を出力することができます。全プロセスはO(1)
時間がかかります。追加のO(n)
スペース/メモリー要件があります。上で提供された 2つの並列アルゴリズムはコメント で言及されるように追加のスペースを必要とするかもしれないことに注意してください。
O(k)
時間およびO(log(k))
空間アルゴリズムがあると思います。利用可能な任意の大きな整数に対してfloor(x)
およびlog2(x)
関数がある場合、
k
-ビット長整数(したがってlog8(k)
スペース)があり、そこにx^2
を追加します。ここで、xはバッグにある次の番号です:s=1^2+2^2+...
This O(N)
時間かかります(インタビュアーにとっては問題ではありません)。最後に、j=floor(log2(s))
を取得します。これは、探している最大数です。次にs=s-j
を実行し、上記を再度実行します。
for (i = 0 ; i < k ; i++)
{
j = floor(log2(s));
missing[i] = j;
s -= j;
}
現在、通常は2756
- bit整数ではなくdoubleの代わりにfloor関数とlog2関数がありません。そう?単純に、2バイト(または1、3、または4)ごとにこれらの関数を使用して目的の数値を取得できますが、これは時間の複雑さにO(N)
要因を追加します
ArrayListオブジェクト(myList)がそれらの数字で満たされているとしましょう。その中で、2つの数字xとyが欠落しています。
int k = 1;
while (k < 100) {
if (!myList.contains(k)) {
System.out.println("Missing No:" + k);
}
k++;
}
これは愚かに聞こえるかもしれませんが、あなたに提示された最初の問題では、その方程式を使用して不足している番号を見つけるために実際にそれらを合計するためにバッグの残りの番号すべてを見なければなりません。
だから、あなたはすべての番号を見ることができるので、足りない番号を探すだけです。 2つの数字が抜けているときも同じです。とてもシンプルだと思います。あなたがバッグに残っている数を見るようになるとき、方程式を使う意味がありません。
これが効率的かどうかはわかりませんが、この解決策を提案したいと思います。
ここでループを実行して、両方とも[1、100]にある可能性のあるペア(p、q)とdの合計を取得します。
ペアが得られたら(3の結果)XOR p = qであるかどうかをチェックし、そうであれば完了です。
私が間違っている場合は私を修正し、これが正しい場合は時間の複雑さについてもコメントしてください