時間でサイズ3のソートされたサブシーケンスを見つけるアルゴリズムはありますか?


21

Iは、証明または反証配列が与えられると、アルゴリズムが存在する整数を三の指数を見つけ、私はJおよびkのように、I < J < KA [ I ] < A [ J ] < A [ kは(または、そのようなトリプルがないことがわかります)線形時間で。Ai,jki<j<kA[i]<A[j]<A[k]

これは宿題の質問ではありません。「そのようなアルゴリズムを実装してみてください」とフレーム化されたプログラミングフォーラムでそれを見ました。さまざまな実験の後では不可能だと思います。私の直感はそう教えてくれますが、それは実際には何の役にも立ちません。

正式に証明したいと思います。どうやってやるの?理想的には、段階的にレイアウトされた証明を見たいと思います。そして、もしあなたがとても傾いているなら、このような簡単な質問を一般的に証明/反証する方法についての説明をご覧ください。それが役立つ場合、いくつかの例:

[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)

反復処理でき、i < j(つまり、現在のj)が存在するたびに、新しいトリプルを作成して配列にプッシュすると想定しました。トリプルの1つが完了するまで、各トリプルのステップ実行と比較を続けます。だから、のようなものです、!しかし、トリプル配列のトリプルの数は最悪の場合入力リストのサイズに対応するため、これは単なるOn よりも複雑だと思います。Ai<jj[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3O(n)



最悪の場合(ソートされた配列)には、多くの適切なトリプルがあることにも注意してください。提案するアルゴリズムを擬似コードとして提供することを検討してください。あなたの説明は完全ではないと思います。Θ(n3)
ラファエル

回答:


14

これは、最長増加サブシーケンス問題のバリエーションです。これは、2つの補助配列およびPを使用してウィキペディアに提示されたソリューションです。MP

  • -格納位置 kの最小値の A [ K ]の長さの増加部分列があるように Jで終わる A [ K ]の範囲で K I(注我々は J K Iここで、ので、 jが増加部分列の長さを表し、 kはその終了の位置を表している。もちろん、我々は長さの増加サブ持つことはありません 13位で終わる 11M[j]kA[k]jA[k]kijkijk1311の定義によります)。ki
  • -の前身の格納位置 A [ K ]で終了する最長の増加サブシーケンスにおける A [ K ]P[k]A[k]A[k]

    さらに、アルゴリズムは、これまでに見つかった最長の増加サブシーケンスの長さを表す変数保存します。L

このアルゴリズムは、最悪の場合の時間ます。あなたの問題は、あなたが返すことができます特殊なケースであるとき、L = 3プッシュがまでランタイムO N のバイナリ検索は、時間にそのためである、2最大で長さの配列上でのみ動作しますので、O 1 とは対照的に、一般的な場合のΘ log n Θ(nlogn)L=3O(n)O(1)Θ(logn)

変更された擬似コードを検討してください。

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L
      such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)
   if L==3 : return true; // you can break here, and return true.
return false; // because L is smaller than 3.

@SaeedAmiriコメントを見ましたが、まだレビューする時間がありませんでした(寝る前に質問を投稿しました)。私はあなたのリンクから、私たちの特別なケースL = 3が何らかの形で役立つと疑ったが、詳細を理解する機会がなかった。私は現在仕事をしていて、時間に制約があります。ご回答いただきありがとうございます。その中のすべての行を完全に理解せずに感謝することは、私にとって表面的なことです。
クリストファー

@SaeedAmiri:ここでもっと「ギャップを埋める」ことを期待していることに同意しますが、少なくとも証拠(しかし大ざっぱな)のコーナー引数を与える必要があります。OPについては、彼はイタリアに拠点を置いているようですので、おそらくあなたのコメントと回答の間で眠っていました(そして、彼は今東で忙しいでしょう)。
ラファエル

@ChristopherDone、私はあなたを動揺させたくない、申し訳ありませんが私の間違いです、あなたは間違いなく正しいです。

O(1)

いいですね 一般的な最長増加シーケンスアルゴリズムの動作を理解するのに時間がかかりました。その後、最大length == 3の変更で問題ありません。ありがとう!
クリストファー

11

方法論に関するメモ

この問題について少し考えて、解決策を見つけました。Saeed Amiriの答えを読んだとき、私が思いついたのは、長さ3のシーケンスの標準的な最長サブシーケンス検索アルゴリズムの特殊バージョンであることに気づきました。問題解決の興味深い例です。

2要素バージョン

i<jA[i]<A[j]

Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

このケースは非常に簡単です。それを一般化しようとします。記載されている問題は解決できないことを示しています。要求されたインデックスが常に存在するとは限りません。そのため、アルゴリズムが有効なインデックスが存在する場合はそれを返すか、そのようなインデックスが存在しないと正しく主張するように依頼します。

アルゴリズムを考え出す

A(A[i1],,A[im])i1<<imA(A[i],A[i+1],,A[i+m1])

要求されたインデックスが常に存在するとは限らないことがわかりました。私たちの戦略は、指標が存在しない場合に研究することです。これを行うには、インデックスを見つけようとして、検索がどのように失敗するかを確認します。次に、検索が失敗しない場合は、インデックスを見つけるためのアルゴリズムを提供します。

4,3,2,1,0

j=i+1k=j+1A[i]<A[i+1]<A[i+2]

4,3,2,1,2,3,2,1,0

A[j]<A[j+1]iA[i]<A[j]kA[j+1]<A[k]

4,3,2,2.5,1.5,0.5,1,0

ik

3,2,1,3.5,2.5,1.5,0.5、-0.5,1.25、-0.25 3,2,1,2.5,1.5,0.5,2,1,0

ijki

2.1、3、2、1、2.5、1.5、0.5、2、1、0 1,2,0,2.5,1.5,0.5

i(i,j)ki(i,j)(i,j)i>jA[i]<A[i]ii(i,j)jA[j]<A[j](i,j)

アルゴリズムの説明

Python構文で与えられますが、テストしていないことに注意してください。

def subsequence3(A):
    """Return the indices of a subsequence of length 3, or None if there is none."""
    index1 = None; value1 = None
    index2 = None; value2 = None
    for i in range(0,len(A)):
        if index1 == None or A[i] < value1:
            index1 = i; value1 = A[i]
        else if A[i] == value1: pass
        else if index2 == None:
            index2 = (index1, i); value2 = (value1, A[i])
        else if A[i] < value2[1]:
            index2[1] = i; value2[1] = A[i]
        else if A[i] > value2[1]:
            return (index2[0], index2[1], i)
    return None

証明スケッチ

index1は、既にトラバースされている配列の部分の最小のインデックスです(複数回出現する場合、最初の出現を保持します)、またはNone最初の要素を処理する前。index2長さが2の増加するサブシーケンスのインデックスを、最小の最大要素を持つ配列の既に通過した部分に格納するかNone、そのようなシーケンスが存在しない場合。

ときにreturn (index2[0], index2[1], i)実行するには、我々が持っているvalue2[0] < value[1](これはの不変であるvalue2)およびvalue[1] < A[i](文脈から明らか)。アーリーリターンを呼び出さずにループが終了した場合、の場合、value1 == None長さ2の増加サブシーケンスはありませんvalue1。後者の場合、さらに、長さ3の増加するサブシーケンスがの前に終了しないという不変式がありvalue1ます。したがって、そのようなサブシーケンスの最後の要素は、に追加されvalue2、長さ3の増加するサブシーケンスを形成します:value2配列のすでに通過した部分に含まれる長さ3の増加するサブシーケンスの一部ではない不変量があるため、配列全体にそのようなサブシーケンスはありません。

前述の不変式の証明は、読者の課題として残されています。

複雑

O(1)O(1)O(n)

正式な証明

読者への演習として残しました。


8

O(n)O(n)

最初に、スタックと、各要素、それよりも大きい要素のインデックス、およびその右側を示す補助配列を維持しながら、配列を左から右に走査します。

1

配列内の新しい要素を検討するたびに、その要素がスタックの一番上の要素よりも大きい場合、スタックからポップし、一番上の要素に対応する補助配列要素を設定して、新しい要素のインデックスを考慮。

現在の要素が大きい間、スタックから要素をポップし、対応するインデックスを設定し続けます。一番上にある要素がより小さくない(または空になる)と、現在の要素をスタックにプッシュし、配列の次の要素に進み、上記の手順を繰り返します。

別のパス(および別の補助配列)を作成しますが、右から左に進みます。

1

O(n)

ki

最初のパスの擬似コードは次のようになります。

Stack <Pair<Elem, Index>> greats;
Elem auxArr[inputArr.Length];

for (Index i = 0; i < inputArr.Length; i++) {

    while (!greats.IsEmpty() && inputArr[i] > greats.PeekTop().Elem) {
        Pair top = greats.Pop();
        auxArr[top.Index] = i;
    }

    Pair p;
    p.Elem = inputArr[i];
    p.Index = i;

    greats.Push(p);
}

「配列の各要素を一定の回数だけ考慮するため、これはO(n)時間です。」ああ、クラム。どういうわけか、複数の定数パスを除外し、O(n)ではないものとして破棄しました。とてもバカ。あなたの説明に感謝し、もう一度解決しようとします。
クリストファー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.