等間隔に並んだ最長の部分配列
質問
100万個の整数をソートして、連続するペアの差が等しい最も長い部分列を見つけたい。例えば
1, 4, 5, 7, 8, 12
は部分配列を持っています。
4, 8, 12
私の素朴な方法は貪欲で、各点からどこまで部分列を伸ばせるかをチェックするだけです。これには
O(n²)
の時間がかかるようです。
もっと早く解決する方法はないのでしょうか?
更新しました。
私はできるだけ早く回答で与えられたコードをテストします(ありがとうございます)。しかし、n^2メモリを使用してもうまくいかないことはすでに明らかです。 今のところ、入力が次のように終了するコードはありません。
[random.randint(0,100000) for r in xrange(200000)]
.
タイミング 私の32ビットシステムで、以下の入力データでテストしました。
a= [random.randint(0,10000) for r in xrange(20000)]
a.sort()
- ZelluXの動的計画法では、1.6GのRAMを使い、2分14秒かかります。 pypyを使えば、わずか9秒で済みます! しかし、大きな入力ではメモリエラーでクラッシュします。
- ArminのO(nd)タイムメソッドは、pypyで9秒かかりましたが、RAMは20MBだけでした。もちろん、範囲がもっと大きければ、これはもっと悪くなります。 メモリ使用量が少ないので、a= [random.randint(0,100000) for r in xrange(200000)] でもテストできましたが、私がpypyで与えた数分では終わりませんでした。
Kluevのメソッドをテストできるようにするために、次のように再実行しました。
a= [random.randint(0,40000) for r in xrange(28000)]
a = list(set(a))
a.sort()
を使うと、おおよその長さのリストを作ることができます。
20000
. すべてのタイミングはpypyで
- ZelluX、9 秒
- Kluev、20秒
- アルミン、52秒
ZelluX方式をリニアな空間にすることができれば、明らかに勝者となるようです。
どのように解決するのですか?
更新しました。 ここで紹介した最初のアルゴリズムは Armin Rigoの2つ目の回答 の方がはるかにシンプルで効率的です。しかし、これらの方法には一つ欠点があります。100万個の整数の結果を見つけるのに何時間もかかるのです。そこで私は、入力される整数の範囲が限定されていると仮定した、さらに2つのバリエーション(この解答の後半を参照)を試してみました。このような制限を設けることで、より高速なアルゴリズムが可能になります。また、Armin Rigoのコードを最適化することも試みました。最後に私のベンチマーク結果をご覧ください。
O(N)メモリを使用したアルゴリズムのアイデアです。時間計算量はO(N 2 log N)であるが、O(N)に減少する可能性がある。 2 ).
アルゴリズムは以下のデータ構造を使用します。
-
prev
: (不完全な)部分配列の前の要素を指すインデックスの配列です。 -
hash
: キー = 部分配列の連続するペアの差、値 = 他の2つのハッシュマップを持つハッシュマップ。これらの他のハッシュマップについて: key = 部分配列の開始/終了インデックス、value = (部分配列の長さ、部分配列の終了/開始インデックス)のペア。 -
pq
に格納された部分配列のすべての可能な "difference" 値のための優先キューです。prev
とhash
.
アルゴリズムです。
-
初期化
prev
インデックスを持つi-1
. 更新hash
とpq
で、このステップで見つかった全ての(不完全な)部分配列とその "差分"を登録することができます。 -
から最小の"difference"を取得(および削除)する。
pq
. から対応するレコードを取得する。hash
から対応するレコードを取得し、第2レベルのハッシュマップの1つをスキャンする。このとき、与えられた "difference" を持つ全ての部分配列が完了する。もし第2レベルのハッシュマップにこれまで見つかったものより良い長さの部分配列があれば、最良の結果を更新する。 -
配列の中で
prev
: 手順 2 で見つかった任意のシーケンスの各要素について、インデックスをデクリメントしてhash
を更新し、場合によってはpq
. を更新している間hash
を更新する際に、長さ1の新しい部分列を追加する、既存の部分列を1つ増やす、既存の2つの部分列を結合する、などの操作を行うことができます。 - 手順2で見つけたハッシュマップレコードを削除します。
-
ステップ #2 から続ける間
pq
が空でない限り続けます。
このアルゴリズムでは、O(N)個の
prev
をそれぞれO(N)回更新します。そして、これらの更新のそれぞれは、新しい "差分" を
pq
. これらのことは、時間の複雑さが O(N
2
log N)であることを意味します。
pq
. これを O(N
2
) に減少させるために,より高度な優先度キュー実装を使用することができます.このページでは、その可能性をいくつか挙げています。
優先度キュー
.
対応する Python コードは イデオン . このコードはリスト内の要素の重複を許しません。これを修正することは可能ですが、重複を削除する(そして重複を超えた最長の部分列を個別に見つける)ことはいずれにせよ良い最適化でしょう。
そして を少し最適化した後、同じコード . ここでは、部分配列の長さと可能な部分配列の差の掛け算がソースリストの範囲を超えたらすぐに検索を終了します。
Armin Rigoのコードはシンプルでかなり効率的です。しかし、いくつかのケースで、回避できる余分な計算をしています。部分配列の長さと可能な部分配列の差の掛け算がソースリストの範囲を超えるとすぐに検索が終了してしまうことがあります。
def findLESS(A):
Aset = set(A)
lmax = 2
d = 1
minStep = 0
while (lmax - 1) * minStep <= A[-1] - A[0]:
minStep = A[-1] - A[0] + 1
for j, b in enumerate(A):
if j+d < len(A):
a = A[j+d]
step = a - b
minStep = min(minStep, step)
if a + step in Aset and b - step not in Aset:
c = a + step
count = 3
while c + step in Aset:
c += step
count += 1
if count > lmax:
lmax = count
d += 1
return lmax
print(findLESS([1, 4, 5, 7, 8, 12]))
元データの整数の範囲(M)が小さい場合、単純なアルゴリズムでO(M 2 )の時間とO(M)の空間を持つ簡単なアルゴリズムが可能です。
def findLESS(src):
r = [False for i in range(src[-1]+1)]
for x in src:
r[x] = True
d = 1
best = 1
while best * d < len(r):
for s in range(d):
l = 0
for i in range(s, len(r), d):
if r[i]:
l += 1
best = max(best, l)
else:
l = 0
d += 1
return best
print(findLESS([1, 4, 5, 7, 8, 12]))
Armin Rigo氏による最初の方法と似ていますが、動的なデータ構造を一切使っていません。ソースデータには重複がないと仮定します。また、(コードをシンプルにするために)最小入力値が非負でゼロに近いと仮定します。
booleanの配列の代わりにbitsetデータ構造とビット演算を使用して並列にデータを処理する場合、以前のアルゴリズムは改善されるかもしれません。以下に示すコードは、Pythonの組み込み整数としてビットセットを実装しています。重複がないこと、入力値の最小値が非負でゼロに近いことは同じ仮定である。時間計算量はO(M 2 * log L) で、Lは最適な部分配列の長さ、空間の複雑さはO(M)です。
def findLESS(src):
r = 0
for x in src:
r |= 1 << x
d = 1
best = 1
while best * d < src[-1] + 1:
c = best
rr = r
while c & (c-1):
cc = c & -c
rr &= rr >> (cc * d)
c &= c-1
while c != 1:
c = c >> 1
rr &= rr >> (c * d)
rr &= rr >> d
while rr:
rr &= rr >> d
best += 1
d += 1
return best
ベンチマークです。
入力データ(約100000個の整数)はこのように生成されます。
random.seed(42)
s = sorted(list(set([random.randint(0,200000) for r in xrange(140000)])))
また、最速のアルゴリズムとして、以下のデータ(約10000個の整数)も使ってみました。
s = sorted(list(set([random.randint(0,2000000) for r in xrange(1400000)])))
結果はすべて秒単位で表示されます。
Size: 100000 1000000
Second answer by Armin Rigo: 634 ?
By Armin Rigo, optimized: 64 >5000
O(M^2) algorithm: 53 2940
O(M^2*L) algorithm: 7 711
関連
-
[解決済み】動的計画法を用いて,最も長く増加する部分列を決定する方法とは?
-
[解決済み】固定長 6 int 配列の最速ソート
-
[解決済み] 前月の日時オブジェクトを返す
-
[解決済み] PythonでのAWS Lambdaのインポートモジュールエラー
-
[解決済み] googletransがエラー 'NoneType' オブジェクトに 'group' 属性がない、と言って動かなくなった。
-
[解決済み] dict を txt ファイルに書き、それを読み取る?
-
[解決済み] ファブリック経由でデプロイユーザとしてvirtualenvを有効化する
-
[解決済み] 文字列のリストを内容に基づいてフィルタリングする
-
[解決済み] Cythonのコードを含むPythonパッケージはどのように構成すればよいのでしょうか?
-
[解決済み] PySparkでデータフレームのカラムをString型からDouble型に変更する方法は?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] SQLAlchemy: セッションの作成と再利用
-
[解決済み] pandasのDataFrameから空のセルを含む行を削除する
-
[解決済み] Pythonでコード行間にかかる時間を測定するには?
-
[解決済み] 文字列のリストを内容に基づいてフィルタリングする
-
[解決済み] スペースがないテキストを単語のリストに分割する方法
-
[解決済み] PyQtアプリケーションのスレッド化。QtスレッドとPythonスレッドのどちらを使うか?
-
[解決済み] PySparkでデータフレームのカラムをString型からDouble型に変更する方法は?
-
[解決済み] djangoのQueryDictをPythonのDictに変更するには?
-
[解決済み] pipの依存性/必要条件をリストアップする方法はありますか?
-
[解決済み] Alembicアップグレードスクリプトでインサートやアップデートを実行するにはどうすればよいですか?