Copyright Statement:この記事はColin Caiによるオリジナルです。再投稿してください。再投稿するには、元のURLを指定する必要があり http://www.cnblogs.com/Colin-Cai/p/12664044.html 著者:窓の QQ /マイクロ手紙:6679072 Eメール:[email protected]
いわゆるモードは、このような問題から来ています。長さがlenの配列で、数値がlen / 2よりも多く表示される場合、この数値を見つける方法。
ソートベース
並べ替えは最初の感じです。つまり、配列を並べ替え、もう一度走査して結果を取得します。
基本的にC言語で書かれています:
int find(int a、int len) { sort(a、len); トラバースを返す(a、len); }
並べ替えには、クイックソート、マージソート、ヒープソートなどのO(nlogn)時間の複雑さのアルゴリズムがあり、結果を取得するために並べ替えられた配列をトラバースすると、時間線形の複雑さ、つまりO(n)になります。したがって、アルゴリズム全体の時間の複雑さはO(nlogn)です。
より良いアルゴリズムを見つける
上記のアルゴリズムは単純すぎるため、多くの場合、私たちの最初の感覚から得られることは必ずしも信頼できるとは限りません。
線形時間レベルアルゴリズム、つまりΘ(n)時間レベルアルゴリズムを見つけることができますか?Θは、上限と下限の同じ記号です。実際、線形時間レベル、つまり時間o(n)よりも低いアルゴリズムがないことを証明するのは簡単です。小さなoは大きなOとは異なり、低次の無限大を意味します。証明はおおよそ次のとおりです。
アルゴリズムがo(n)時間の複雑さで上記の問題を解決できる場合。これはnよりも低い次数の無限大なので、長さNの配列が必要です。このアルゴリズムを完了すると、配列で検出された要素はN / 2未満になります。アルゴリズム演算の結果がaであると仮定すると、配列の演算中に検出されなかったこの配列のすべての要素を、アルゴリズムの結果ではない同じ番号bに置き換えます。次に、アルゴリズムによって新しい配列が計算されます。検出されなかった数はアルゴリズムの結果に影響を与えないため、結果は自然にaになりますが、実際には、N / 2回を超える配列の出現回数はbです。これは矛盾を引き起こすため、この問題のo(n)時間アルゴリズムは存在しません。
これで、もっと深いものについて考え始めることができます。
最初に、配列に2つの異なる数値がある場合は、配列から2つの数値を削除して新しい配列を取得します。その後、新しい配列のモードは古い配列と同じです。これは簡単に証明できます:
配列がa、長さがlen、モードがx、出現回数がtであるとします。もちろん、t> len / 2が満たされます。yとzの2つの数値があるとします。y≠zです。これらの2つの数値を削除すると、残りの配列の長さはlen-2になります。これらの2つの数値がモードxに等しくない場合、つまりx≠yとx≠zの場合、xが新しい配列に現れる回数はまだtです。t> len / 2>(len-2)/ 2したがって、tはまだ新しい配列のモードです。そして、xがこれらの2つの数値に存在する場合、当然xは1つだけであり、残りの配列のxの出現数はt-1、t-1> len / 2-1 =(len-2)/ 2なので、 xは新しい配列のモードです。
上記の考えで、このペアの異なる数を見つける方法を考えます。
次のフローチャートに従って、数値numとそれが繰り返される回数を記録し、配列を1回トラバースできます。
num / timesは常に数とその繰り返しを記録しています。Timesプラス1とマイナス1は、配列の新しい数がnumと同じかどうかによって決まります。マイナス1の状況は、上で証明された命題に依存します。異なる数値の場合、これら2つを削除すると、残りのアレイのモードは変更されません。
ポイントは、最終結果が必要なモードであることを証明することです。後者の結果がモードではない場合、モードが表示されるたびに、モードではない数値で「オフセット」する必要があります。そのため、配列内の非モード番号の数はモードの数より少なくなりませんが、これは現実ではありません。したがって、上記のアルゴリズムが確立され、線形の時間複雑度O(n)および一定の空間複雑度O(1)を持ちます。
C言語コードは基本的に次のとおりです。
int find(int * a、int len) {
int i、num = 0、times = 0; for(i = 0 ; i <len; i ++ ){ if(times> 0 ){ if(num == a [i]) times ++ ; 他の 時間 - ; } else { num = a [i]; 回 = 1 ; } } return num; }
Schemeで記述した場合、プログラムは次のように簡潔にすることができます。
(define(find s) (car (fold - right (lambda(nr)) (if(zero?(cdr r)) (cons n 1 ) (cons(car r)((if(eq?n(car r))) +-)(cdr r)1 )))) ' (()。0)s)))
アップグレード後の問題
上記のモードは配列の長さの1/2を超えています。1/ 2を1/3に変更すると、どのようにしてそれを見つけることができますか?
たとえば、配列が[1、1、2、3、4]の場合、検索されるモードは1です。
もう一度昇華しましょう。1/ mの場合、mはパラメーターです。この質問は以前よりも複雑になります。さらに、問題がアップグレードされた後、[1、1、2、2、3]のように複数のモードが存在する可能性があることを認識しておく必要があります。 5/3より大きい。最大でm-1モードがあります。
アイデア
それがまだソートされてからトラバースされる場合、それはまだ有効ですが、時間の複雑さは依然としてO(nlogn)レベルです。
最初の質問では、確立の前提は、配列内の2つの異なる数を削除することであり、モードは変更されません。したがって、アップグレード後も同様の結果が得られますか?以前とは異なり、モードが1/2以上から1 / m以上に変更されたときに何が起こるかを調べ、長さlenの配列aからm個の異なる数を削除する方法を調べます。証明プロセスは次のとおりです。
同様に、aにモードxがあり、xの出現回数がtであると想定します。m個の異なる数を削除した後、xがモードでないかどうかを確認します。m個の数値を削除すると、新しい配列の長さはlen-mになります。xはモードなので、xの出現回数はt> len / mです。削除されたm個の数値にxsがない場合でも、残りの配列のxの出現回数はt、t> len / m>(len-m )/ m、したがってこの場合xはモードです;削除されたm個の数値にxがある場合、m個の数値は互いに異なるため、xは1つだけなので、残りの配列でのxの出現数はtです。 -1、t> len / m、つまりt-1> len / m-1 =(len-m)/ mなので、xは残りの配列で依然として過半数です。上記は、配列内のすべてのモードに当てはまります。同様に、配列に含まれていない数値については、残りの配列がまだモードになっていないことが証明できますが、実際には上記すべてを≤に置き換えます。
上記の理解があれば、前のアルゴリズムに従うことができますが、ここでは長さが最大n-1のリンクリストに変更されています。たとえば、配列[1、2、1、3]の場合、モード1は配列の長さ4の1/3を超えます。プロセスは次のとおりです。
最初は、空のリスト[]
最初の要素1を取得し、リンクリストにnum = 1のエントリがないこと、およびリンクリストの長さが2に達していないことを確認するため、リンクリストに挿入して[(num = 1、times = 1)]を取得します。
2番目の要素2を取得し、リンクリストにnum = 2のレコード要素がないことを確認します。リンクリストの長さが2に到達せず、リンクリストに挿入して、[(num = 1、times = 1)、(num = 2、times = 1 )]
3番目の要素1を取得し、リンクされたリストにnum = 1のエントリが既に存在することを確認してから、エントリ時間に1を加算して[(num = 1、times = 2)、(num = 2、times = 1)]を取得します
4番目の要素3を取得すると、リンクリストにnum = 3のテーブル要素がないことがわかります。リンクリストの長さが2に等しい最大値に達しているため、削除が実行されます。つまり、各テーブル要素の時間が1だけ減り、テーブル要素が0に減少します。リンクされたリストから移動して、[(num = 1、times = 1)]を取得します
上記はプロセスであり、最後にモードは1です。
上記のプロセスによって最終的に取得されたリンクリストには、すべてのモードが含まれています。これは、どのモードの時間も完全にキャンセルできないため、簡単に証明できます。ただし、上記のプロセスは最終的なリストがすべてのモードになることを実際に保証するものではありません。たとえば、[1,1,2,3,4]は最終的に[(num = 1、times = 1)、(num = 4、times = 1)]ですが、4はモードではありません。
したがって、リンクリストを取得した後、もう一度配列を走査し、リンクリストの繰り返し回数を記録する必要があります。
Pythonは、map / reduce高次関数を使用して手続き型ループを置き換えます。上記のアルゴリズムでは、次のように多くのコードも必要です。
from functools import reduce def find(a、m): def find_index(arr、test): for i in range(len(arr)): if test(arr [i]): return i return -1 def check(r、 n): index = find_index(r、lambda x:x [0] == n) if index> = 0: r [index] [ 1] + = 1 return r if len(r)<m-1 : return r + [[n、1]] return reduce(lambda arr、x:arr if x [1] == 1 else arr + [[x [0]、x [1] -1 ]]、r、[]) def count(r、n): index = find_index(r、lambda x:x [0] == n) if index < 0: return r r [index] [ 1] + = 1 return r return reduce(lambda r、x:r + [x [0] ] if x [1]> len(a)// m else r、\ reduce(count、a、\ list(map(lambda x:[x [0]、0]、reduce(check、a、[]))))、[])
コードがC言語で記述されている場合、コードは多くなりますが、リンクリストは使用できず、固定長配列を使用する方がはるかに効率的です。Times= 0は、要素が占有されていないことを意味します。これはここでは実現されず、興味のある読者が自分で実現できるようにします。