web-dev-qa-db-ja.com

最も長く増加するサブシーケンス

入力シーケンスが与えられた場合、最も長い(必ずしも連続的ではない)非減少サブシーケンスを見つけるための最良の方法は何ですか。

0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 # sequence

1, 9, 13, 15 # non-decreasing subsequence

0, 2, 6, 9, 13, 15 # longest non-deceasing subsequence (not unique)

最高のアルゴリズムを探しています。コードがあれば、Python=でいいでしょうが、何でもかまいません。

31
Jungle Hunter

私はこの問題に遭遇し、これを思いついたPython 3実装:

_def subsequence(seq):
    if not seq:
        return seq

    M = [None] * len(seq)    # offset by 1 (j -> j-1)
    P = [None] * len(seq)

    # Since we have at least one element in our list, we can start by 
    # knowing that the there's at least an increasing subsequence of length one:
    # the first element.
    L = 1
    M[0] = 0

    # Looping over the sequence starting from the second element
    for i in range(1, len(seq)):
        # Binary search: we want the largest j <= L
        #  such that seq[M[j]] < seq[i] (default j = 0),
        #  hence we want the lower bound at the end of the search process.
        lower = 0
        upper = L

        # Since the binary search will not look at the upper bound value,
        # we'll have to check that manually
        if seq[M[upper-1]] < seq[i]:
            j = upper

        else:
            # actual binary search loop
            while upper - lower > 1:
                mid = (upper + lower) // 2
                if seq[M[mid-1]] < seq[i]:
                    lower = mid
                else:
                    upper = mid

            j = lower    # this will also set the default value to 0

        P[i] = M[j-1]

        if j == L or seq[i] < seq[M[j]]:
            M[j] = i
            L = max(L, j+1)

    # Building the result: [seq[M[L-1]], seq[P[M[L-1]]], seq[P[P[M[L-1]]]], ...]
    result = []
    pos = M[L-1]
    for _ in range(L):
        result.append(seq[pos])
        pos = P[pos]

    return result[::-1]    # reversing
_

アルゴリズムがどのように機能するかを理解するのに少し時間がかかったので、コメントを少し冗長にして、簡単な説明も追加します。

  • seqは入力シーケンスです。
  • Lは数値です。シーケンスをループしている間に更新され、その瞬間までに見つかった最も長いインクリメントサブシーケンスの長さをマークします。
  • Mはリストです。 _M[j-1]_は、長さseqの増加するサブシーケンスを構築するために(最後に)使用できる最小値を保持するjのインデックスを指します。
  • Pはリストです。 _P[i]_は_M[j]_を指します。ここで、iseqのインデックスです。一言で言えば、それはサブシーケンスの前の要素がどれであるかを示します。 Pは、最後に結果を作成するために使用されます。

アルゴリズムのしくみ:

  1. 空のシーケンスの特殊なケースを処理します。
  2. 1要素のサブシーケンスから始めます。
  3. インデックスiの入力シーケンスをループします。
  4. バイナリ検索で、_seq[M[j]_を_<_より_seq[i]_にするjを見つけます。
  5. PMおよびLを更新します。
  6. 結果をトレースバックして、元に戻します。

注:wikipediaアルゴリズム との唯一の違いは、Mリストの1のオフセットであり、そのXはここではseqと呼ばれます。私は Eric Gustavsonの回答 に示されているもののわずかに改善された単体テストバージョンでもテストし、すべてのテストに合格しました。


例:

_seq = [30, 10, 20, 50, 40, 80, 60]

       0    1   2   3   4   5   6   <-- indexes
_

最後に、次のようにします。

_M = [1, 2, 4, 6, None, None, None]
P = [None, None, 1, 2, 2, 4, 4]
result = [10, 20, 40, 60]
_

ご覧のとおり、Pは非常に単純です。末尾から見る必要があるので、_60_の前に_40,_の前に_80_があり、_40_の前に_40_があると_20_があることがわかります、_50_の前に_20_があり、_20_の前に_10_があります。

複雑な部分はMにあります。最初はMは_[0, None, None, ...]_でした。これは、長さ1のサブシーケンスの最後の要素(したがって、Mの位置0)がインデックス0:_30_にあったためです。

この時点で、seqでループを開始し、_10_が_10_より_<_であるため、_30_を調べます。Mは更新します:

_if j == L or seq[i] < seq[M[j]]:
    M[j] = i
_

したがって、Mは次のようになります:_[1, None, None, ...]_。 _10_には、より長いサブシーケンスを作成するためのチャンクが多いため、これは良いことです。 (新しい1は10のインデックスです)

これが_20_の番です。 _10_および_20_を使用すると、長さ2(Mのインデックス1)のサブシーケンスがあるため、Mは_[1, 2, None, ...]_になります。 (新しい2は20のインデックスです)

これが_50_の番です。 _50_はサブシーケンスの一部ではないため、何も変更されません。

これが_40_の番です。 _10_、_20_、および_40_を使用すると、長さ3のサブ(Mのインデックス2)があるため、Mは次のようになります:_[1, 2, 4, None, ...]_。(新しい4は40のインデックスです)

等々...

コードを完全にウォークするには、コードをコピーして貼り付けます here :)

29
Rik Poggi

Mathematicaで最も長い増加/減少するサブシーケンスを簡単に見つける方法は次のとおりです:

 LIS[list_] := LongestCommonSequence[Sort[list], list];
 input={0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
 LIS[input]
 -1*LIS[-1*input]

出力:

{0, 2, 6, 9, 11, 15}
{12, 10, 9, 5, 3}

Mathematicaには、Combinatorica`ライブラリにLongestIncreasingSubsequence関数も含まれています。 Mathematicaがない場合は WolframAlpha をクエリできます。

C++ O(nlogn)ソリューション

いくつかの観察に基づいたO(nlogn)解もあります。要素aを使用して、長さjのすべての増加するサブシーケンスの中でAi、jを可能な最小のテールとします1、a2、...、a。特定のiについて、Ai、1、Ai、2、...、A私、j。これは、ai + 1で終わる最長のサブシーケンスが必要な場合、Ai、j <ai + 1 <= Ai、j + 1で長さがj + 1になるようなajを探すだけでよいことを示唆しています。この場合、k!= j + 1の場合、Ai + 1、j + 1はai + 1に等しく、すべてのAi + 1、kはAi、kに等しくなります。さらに、セットAiとセットAi + 1の間には、この検索によって引き起こされる最大で1つの違いがあります。 Aは常に昇順で並べられ、操作によってこの順序は変更されないため、aごとにバイナリ検索を実行できます。1、a2、...、a

実装 C++ (O(nlogn)アルゴリズム)

#include <vector>
using namespace std;

/* Finds longest strictly increasing subsequence. O(n log k) algorithm. */
void find_lis(vector<int> &a, vector<int> &b)
{
  vector<int> p(a.size());
  int u, v;

  if (a.empty()) return;

  b.Push_back(0);

  for (size_t i = 1; i < a.size(); i++) {
      if (a[b.back()] < a[i]) {
          p[i] = b.back();
          b.Push_back(i);
          continue;
      }

      for (u = 0, v = b.size()-1; u < v;) {
          int c = (u + v) / 2;
          if (a[b[c]] < a[i]) u=c+1; else v=c;
      }

      if (a[i] < a[b[u]]) {
          if (u > 0) p[i] = b[u-1];
          b[u] = i;
      }   
  }

  for (u = b.size(), v = b.back(); u--; v = p[v]) b[u] = v;
}

/* Example of usage: */
#include <cstdio>
int main()
{
  int a[] = { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 };
  vector<int> seq(a, a+sizeof(a)/sizeof(a[0]));
  vector<int> lis;
        find_lis(seq, lis);

  for (size_t i = 0; i < lis.size(); i++)
      printf("%d ", seq[lis[i]]);
        printf("\n");    

  return 0;
}

出典: link

私はC++実装をJavaに少し前に書き直しましたが、それが機能することを確認できます。pythonのベクター代替案はリストです。しかし、テストしたい場合あなた自身、ここに実装例が読み込まれたオンラインコンパイラのリンクがあります: link

データの例:{ 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 }と回答:1 3 4 5 6 7

8
Margus

これはかなり一般的な解決策です:

  • O(n log n)時間で実行され、
  • 増加、非減少、減少、非増加のサブシーケンスを処理し、
  • listnumpy.arraystrなどを含むすべてのシーケンスオブジェクトで機能します。
  • 組み込みkey関数のパラメーターと同様に機能するsortedパラメーターを介して、オブジェクトのリストとカスタム比較メソッドをサポートします。
  • サブシーケンスの要素またはそれらのインデックスを返すことができます。

コード:

from bisect import bisect_left, bisect_right
from functools import cmp_to_key

def longest_subsequence(seq, mode='strictly', order='increasing',
                        key=None, index=False):

  bisect = bisect_left if mode.startswith('strict') else bisect_right

  # compute keys for comparison just once
  rank = seq if key is None else map(key, seq)
  if order == 'decreasing':
    rank = map(cmp_to_key(lambda x,y: 1 if x<y else 0 if x==y else -1), rank)
  rank = list(rank)

  if not rank: return []

  lastoflength = [0] # end position of subsequence with given length
  predecessor = [None] # penultimate element of l.i.s. ending at given position

  for i in range(1, len(seq)):
    # seq[i] can extend a subsequence that ends with a lesser (or equal) element
    j = bisect([rank[k] for k in lastoflength], rank[i])
    # update existing subsequence of length j or extend the longest
    try: lastoflength[j] = i
    except: lastoflength.append(i)
    # remember element before seq[i] in the subsequence
    predecessor.append(lastoflength[j-1] if j > 0 else None)

  # trace indices [p^n(i), ..., p(p(i)), p(i), i], where n=len(lastoflength)-1
  def trace(i):
    if i is not None:
      yield from trace(predecessor[i])
      yield i
  indices = trace(lastoflength[-1])

  return list(indices) if index else [seq[i] for i in indices]

コードを自慢して見せるために、上に貼り付けなかった関数のドキュメント文字列を書きました。

"""
Return the longest increasing subsequence of `seq`.

Parameters
----------
seq : sequence object
  Can be any sequence, like `str`, `list`, `numpy.array`.
mode : {'strict', 'strictly', 'weak', 'weakly'}, optional
  If set to 'strict', the subsequence will contain unique elements.
  Using 'weak' an element can be repeated many times.
  Modes ending in -ly serve as a convenience to use with `order` parameter,
  because `longest_sequence(seq, 'weakly', 'increasing')` reads better.
  The default is 'strict'.
order : {'increasing', 'decreasing'}, optional
  By default return the longest increasing subsequence, but it is possible
  to return the longest decreasing sequence as well.
key : function, optional
  Specifies a function of one argument that is used to extract a comparison
  key from each list element (e.g., `str.lower`, `lambda x: x[0]`).
  The default value is `None` (compare the elements directly).
index : bool, optional
  If set to `True`, return the indices of the subsequence, otherwise return
  the elements. Default is `False`.

Returns
-------
elements : list, optional
  A `list` of elements of the longest subsequence.
  Returned by default and when `index` is set to `False`.
indices : list, optional
  A `list` of indices pointing to elements in the longest subsequence.
  Returned when `index` is set to `True`.
"""

いくつかの例:

>>> seq = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]

>>> longest_subsequence(seq)
[0, 2, 6, 9, 11, 15]

>>> longest_subsequence(seq, order='decreasing')
[12, 10, 9, 5, 3]

>>> txt = ("Given an input sequence, what is the best way to find the longest"
               " (not necessarily continuous) non-decreasing subsequence.")

>>> ''.join(longest_subsequence(txt))
' ,abdegilnorsu'

>>> ''.join(longest_subsequence(txt, 'weak'))
'              ceilnnnnrsssu'

>>> ''.join(longest_subsequence(txt, 'weakly', 'decreasing'))
'vuutttttttssronnnnngeee.'

>>> dates = [
...   ('2015-02-03', 'name1'),
...   ('2015-02-04', 'nameg'),
...   ('2015-02-04', 'name5'),
...   ('2015-02-05', 'nameh'),
...   ('1929-03-12', 'name4'),
...   ('2023-07-01', 'name7'),
...   ('2015-02-07', 'name0'),
...   ('2015-02-08', 'nameh'),
...   ('2015-02-15', 'namex'),
...   ('2015-02-09', 'namew'),
...   ('1980-12-23', 'name2'),
...   ('2015-02-12', 'namen'),
...   ('2015-02-13', 'named'),
... ]

>>> longest_subsequence(dates, 'weak')

[('2015-02-03', 'name1'),
 ('2015-02-04', 'name5'),
 ('2015-02-05', 'nameh'),
 ('2015-02-07', 'name0'),
 ('2015-02-08', 'nameh'),
 ('2015-02-09', 'namew'),
 ('2015-02-12', 'namen'),
 ('2015-02-13', 'named')]

>>> from operator import itemgetter

>>> longest_subsequence(dates, 'weak', key=itemgetter(0))

[('2015-02-03', 'name1'),
 ('2015-02-04', 'nameg'),
 ('2015-02-04', 'name5'),
 ('2015-02-05', 'nameh'),
 ('2015-02-07', 'name0'),
 ('2015-02-08', 'nameh'),
 ('2015-02-09', 'namew'),
 ('2015-02-12', 'namen'),
 ('2015-02-13', 'named')]

>>> indices = set(longest_subsequence(dates, key=itemgetter(0), index=True))

>>> [e for i,e in enumerate(dates) if i not in indices]

[('2015-02-04', 'nameg'),
 ('1929-03-12', 'name4'),
 ('2023-07-01', 'name7'),
 ('2015-02-15', 'namex'),
 ('1980-12-23', 'name2')]

この回答は、一部は Code Reviewでの質問 に触発され、一部は 「シーケンス外」の値に関する質問 に触発されました。

3
arekolek

ここにいくつかのpython O(n * log(n)で実行されるアルゴリズムを実装するテスト付きのコード)があります。これは wikipediaトークページ で見つかりました 最長のサブシーケンス

import unittest


def LongestIncreasingSubsequence(X):
    """
    Find and return longest increasing subsequence of S.
    If multiple increasing subsequences exist, the one that ends
    with the smallest value is preferred, and if multiple
    occurrences of that value can end the sequence, then the
    earliest occurrence is preferred.
    """
    n = len(X)
    X = [None] + X  # Pad sequence so that it starts at X[1]
    M = [None]*(n+1)  # Allocate arrays for M and P
    P = [None]*(n+1)
    L = 0
    for i in range(1,n+1):
        if L == 0 or X[M[1]] >= X[i]:
            # there is no j s.t. X[M[j]] < X[i]]
            j = 0
        else:
            # binary search for the largest j s.t. X[M[j]] < X[i]]
            lo = 1      # largest value known to be <= j
            hi = L+1    # smallest value known to be > j
            while lo < hi - 1:
                mid = (lo + hi)//2
                if X[M[mid]] < X[i]:
                    lo = mid
                else:
                    hi = mid
            j = lo

        P[i] = M[j]
        if j == L or X[i] < X[M[j+1]]:
            M[j+1] = i
            L = max(L,j+1)

    # Backtrack to find the optimal sequence in reverse order
    output = []
    pos = M[L]
    while L > 0:
        output.append(X[pos])
        pos = P[pos]
        L -= 1

    output.reverse()
    return output

# Try small lists and check that the correct subsequences are generated.

class LISTest(unittest.TestCase):
    def testLIS(self):
        self.assertEqual(LongestIncreasingSubsequence([]),[])
        self.assertEqual(LongestIncreasingSubsequence(range(10,0,-1)),[1])
        self.assertEqual(LongestIncreasingSubsequence(range(10)),range(10))
        self.assertEqual(LongestIncreasingSubsequence(\
            [3,1,4,1,5,9,2,6,5,3,5,8,9,7,9]), [1,2,3,5,8,9])

unittest.main()
2
zzz
    int[] a = {1,3,2,4,5,4,6,7};
    StringBuilder s1 = new StringBuilder();
    for(int i : a){
     s1.append(i);
    }       
    StringBuilder s2 = new StringBuilder();
    int count = findSubstring(s1.toString(), s2);       
    System.out.println(s2.reverse());

public static int findSubstring(String str1, StringBuilder s2){     
    StringBuilder s1 = new StringBuilder(str1);
    if(s1.length() == 0){
        return 0;
    }
    if(s2.length() == 0){
        s2.append(s1.charAt(s1.length()-1));
        findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2);           
    } else if(s1.charAt(s1.length()-1) < s2.charAt(s2.length()-1)){ 
        char c = s1.charAt(s1.length()-1);
        return 1 + findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2.append(c));
    }
    else{
        char c = s1.charAt(s1.length()-1);
        StringBuilder s3 = new StringBuilder();
        for(int i=0; i < s2.length(); i++){
            if(s2.charAt(i) > c){
                s3.append(s2.charAt(i));
            }
        }
        s3.append(c);
        return Math.max(findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2), 
                findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s3));
    }       
    return 0;
}
1
benben

Javaでのコードと説明は次のとおりです。pythonを間もなく追加する予定です。

arr = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
  1. リスト= {0}-リストを空のセットに初期化します
  2. リスト= {0,8}-新しい最大のLIS
  3. リスト= {0、4}-8を4に変更
  4. リスト= {0、4、12}-新しい最大のLIS
  5. リスト= {0、2、12}-4を2に変更
  6. リスト= {0、2、10}-12を10に変更
  7. リスト= {0、2、6}-10を6に変更
  8. リスト= {0、2、6、14}-新しい最大のLIS
  9. リスト= {0、1、6、14}-2を1に変更
  10. リスト= {0、1、6、9}-14を9に変更
  11. リスト= {0、1、5、9}-6から5に変更
  12. リスト= {0、1、6、9、13}-3から2に変更
  13. リスト= {0、1、3、9、11}-新しい最大のLIS
  14. リスト= {0、1、3、9、11}-9から5に変更
  15. リスト= {0、1、3、7、11}-新しい最大のLIS
  16. リスト= {0、1、3、7、11、15}-新しい最大のLIS

したがって、LISの長さは6(リストのサイズ)です。

import Java.util.ArrayList;
import Java.util.List;


public class LongestIncreasingSubsequence {
    public static void main(String[] args) {
        int[] arr = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
        increasingSubsequenceValues(arr);
    }

    public static void increasingSubsequenceValues(int[] seq) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < seq.length; i++) {
            int j = 0;
            boolean elementUpdate = false;
            for (; j < list.size(); j++) {
                if (list.get(j) > seq[i]) {
                    list.add(j, seq[i]);
                    list.remove(j + 1);
                    elementUpdate = true;
                    break;
                }
            }
            if (!elementUpdate) {
                list.add(j, seq[i]);
            }
        }
        System.out.println("Longest Increasing Subsequence" + list);
    }


}

上記のコードの出力:最長増加サブシーケンス[0、1、3、7、11、15]

1
Deepak Singhvi
def longest_sub_seq(arr):
    main_arr = []
    sub_arr = []
    n = len(arr)
    for ind in range(n):
        if ind < n - 1 and arr[ind] <= arr[ind+1]:
           sub_arr.append(arr[ind])
        else:
           sub_arr.append(arr[ind])
           main_arr.append(sub_arr)
           sub_arr = []
    return max(main_arr, key=len)

a = [3, 10, 3, 11, 4, 5, 6, 7, 8, 12, 1, 2, 3]
print(longest_sub_seq(a)) # op: [4, 5, 6, 7, 8, 12]
0
Goutham raju

コードにはいくつかの答えがありますが、私はそれらを理解するのが少し難しいと感じたので、ここでは、すべての最適化を省いて、一般的な考え方を説明します。最適化については後で説明します。

シーケンス2、8、4、12、3、10を使用します。わかりやすくするために、入力シーケンスが空にならず、同じ番号を2回以上含めないようにする必要があります。

シーケンスを順番に実行します。

その際、一連のシーケンスを維持します。これは、各長さについてこれまでに見つけた最良のシーケンスです。入力シーケンスの最初の要素である長さ1の最初のシーケンスを見つけた後、1からこれまでに見つけた最長までの可能な各長さのシーケンスのセットがあることが保証されます。長さ3のシーケンスがある場合、そのシーケンスの最初の2つの要素は長さ2のシーケンスであるため、これは明らかです。

したがって、最初の要素は長さ1のシーケンスであり、セットは次のようになります。

 1: 2

シーケンスの次の要素(8)を取り、追加できる最も長いシーケンスを探します。これはシーケンス1なので、

1: 2
2: 2 8

シーケンスの次の要素(4)を取得し、追加できる最も長いシーケンスを探します。追加できる最も長いシーケンスは、長さ1のシーケンスです(これは2です)。 これは、私がトリッキーな(または少なくとも非自明な)部分であることがわかったものです。シーケンスの最後に追加できなかったため長さ2(2 8)つまり、長さ2の候補を終了するには、より適切な選択である必要があります。要素が8より大きい場合、長さ2のシーケンスに追加され、新しい長さ3のシーケンスが与えられます。したがって、8未満であることはわかっているため、8を4に置き換えます。

アルゴリズム的に言うと、要素に追加できる最長のシーケンスが何であれ、そのシーケンスとこの要素の合計が、結果として得られる長さのシーケンスの最良の候補です。 処理するすべての要素はどこかに属している必要があることに注意してください(入力で重複する数値を除外したため)。それが長さ1の要素よりも小さい場合は、新しい長さ1になります。それ以外の場合は、既存のシーケンスの最後に移動します。ここでは、長さ1のシーケンスと要素4を足したものが新しい長さ2のシーケンスになります。そして私たちは持っています:

1: 2
2: 2 4 (replaces 2 8)

次の要素12は、長さ3のシーケンスを提供します。

1: 2
2: 2 4
3: 2 4 12

次の要素3は、長さ2のより良いシーケンスを提供します。

1: 2
2: 2 3 (replaces 2 4)
3: 2 4 12

長さが3のシーケンスを変更できないことに注意してください(4を3に置き換えます)。これらは入力シーケンスでこの順序で発生しなかったためです。次の要素である10がこれを処理します。 10でできることは2 3に追加することなので、長さ3の新しいリストになります。

1: 2
2: 2 3
3: 2 3 10 (replaces 2 4 12)

アルゴリズムに関しては、候補となるシーケンスの最後の要素の前にあるものは実際には気にしませんが、最後に完全なシーケンスを出力できるように追跡する必要があることに注意してください。

このように入力要素を処理し続けます。できる限り長いシーケンスに1つずつタックして、結果の長さの新しい候補シーケンスを作成します。これは、その長さの既存のシーケンスよりも悪くないことが保証されているためです。最後に、見つかった最も長いシーケンスを出力します。

最適化

1つの最適化は、実際に各長さのシーケンス全体を格納する必要がないことです。これを行うには、O(n ^ 2)のスペースが必要です。ほとんどの場合、比較するのはこれだけなので、各シーケンスの最後の要素を格納するだけで済みます。 (これで少しだけでは十分でない理由を説明します。説明する前に、その理由を理解できるかどうか確認してください。)

したがって、シーケンスのセットを配列Mとして保存するとします。ここで、M[x]は、シーケンスxの最後の要素を保持します。考えてみれば、Mの要素自体が昇順になっていることに気付くでしょう。それらはソートされています。 M[x+1]M[x]より小さい場合は、代わりにM[x]が置き換えられます。

Mはソートされているので、次の最適化は、私が上で完全に説明したものに行きます:追加するシーケンスをどのように見つけるか?まあ、Mはソートされているので、バイナリ検索を実行して、追加する要素よりも小さい最大のM[x]を見つけることができます。これが、追加するシーケンスです。

これは、最長のシーケンスの長さを求めるだけの場合に最適です。ただし、Mは、シーケンス自体を再構築するには不十分です。ある時点で、セットは次のようになったことを覚えておいてください。

1: 0
2: 0 2
3: 0 4 12

M自体をシーケンスとして出力することはできません。シーケンスを再構築できるようにするには、さらに情報が必要です。このため、さらに2つの変更を加えます最初に、入力シーケンスを配列seqに格納し、要素の値をM[x]に格納する代わりに、要素のインデックスをseqに格納するため、値はseq[M[x]]です。

これを行うのは、サブシーケンスをチェーンすることによってシーケンス全体の記録を保持できるようにするためです。最初に見たように、すべてのシーケンスは、既存のシーケンスの最後に単一の要素を追加することによって作成されます。したがって、second、最後の要素のインデックス(P)を格納する別の配列seqを保持します追加するシーケンスチェーン可能にするために、Pに格納しているのはseqのインデックスなので、P自体にseqのインデックスでインデックスを付ける必要があります。 。

これが機能する方法は、iの要素seqを処理するときに、追加するシーケンスを見つけることです。長さxのシーケンスにseq[i]を付加して、一部のxに対して長さx+1の新しいシーケンスを作成し、iseq[i]M[x+1]ではありません。後で、x+1が可能な最大の長さであることがわかったときに、シーケンスを再構築する必要がありますが、開始点はM[x+1]のみです。

M[x+1] = iP[i] = M[x]P[M[x+1]] = M[x]と同じ)を設定します。つまり、追加するすべての要素iに、iを最長チェーンの最後の要素として使用し、拡張するチェーンの最後の要素のインデックスをP[i]に格納します。だから私たちは:

last element: seq[M[x]]
 before that: seq[P[M[x]]]
 before that: seq[P[P[M[x]]]]
 etc...

これで完了です。これを実際のコードと比較したい場合は、 otherexamples を参照してください。主な違いは、jの代わりにxを使用することであり、長さのリストjM[j-1]ではなくM[j]に格納して、スペースはM[0]にあり、Xではなく入力シーケンスseqを呼び出すことがあります。

0
Old Pro

これは、「列挙」を使用したコンパクトな実装です

def lis(l):

# we will create a list of lists where each sub-list contains
# the longest increasing subsequence ending at this index
lis = [[e] for e in l]
# start with just the elements of l as contents of the sub-lists

# iterate over (index,value) of l
for i, e in enumerate(l):
    # (index,value) tuples for elements b where b<e and a<i
    lower_tuples = filter(lambda (a,b): b<e, enumerate(l[:i]))
    # if no such items, nothing to do
    if not lower_tuples: continue
    # keep the lis-es of such items
    lowerlises = [lis[a] for a,b in  lower_tuples ]
    # choose the longest one of those and add
    # to the current element's lis
    lis[i] = max(lowerlises, key=len) + [e]

# retrun the longest of lis-es
return max(lis, key=len)
0
cmantas

このための最も効率的なアルゴリズムは、O(NlogN)概説 ここ です。

これを解決する別の方法は、元の配列の longest common subsequence (LCS)を使用することです。これは、O(N2)時間。

0
codaddict

これが問題の私のC++ソリューションです。解決策は、これまでに提供されたすべてのものよりも単純で、高速です:N*log(N)アルゴリズムの時間の複雑さ。 leetcodeでソリューションを提出しました。4ミリ秒で実行され、提出されたC++ソリューションの100%よりも高速です。

enter image description here

考えは(私の意見では)明確です。指定された数値の配列を左から右にトラバースします。追加のシーケンスを保持する数値の配列(コードではseq)を追加で維持します。取得した数が、サブシーケンスが保持するすべての数よりも大きい場合は、seqの最後に置き、サブシーケンスの長さカウンターを1増やします。この数が、これまでのサブシーケンスの最大数よりも小さい場合、とにかくそれをseqに入れて、それが属する場所に置いて、既存の数値を置き換えることによってサブシーケンスをソートし続けます。サブシーケンスは、元の数値配列の長さと初期値-infで初期化されます。これは、特定のOSで最小のintを意味します。

例:

数値= {10、9、2、5、3、7、101、18}

seq = {-inf、-inf、-inf、-inf、-inf、-inf、-inf}

数値を左から右にトラバースすると、シーケンスがどのように変化するかを次に示します。

 seq = {10, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {9, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {2, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 5, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 3, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 3, 7, -inf, -inf, -inf, -inf}
 seq = {2, 3, 7, 101, -inf, -inf, -inf}
 seq = {2, 3, 7, 18, -inf, -inf, -inf}

配列の最も長く増加するサブシーケンスの長さは4です。

これがコードです:

int longestIncreasingSubsequence(const vector<int> &numbers){
    if (numbers.size() < 2)
        return numbers.size();
    vector<int>seq(numbers.size(), numeric_limits<int>::min());
    seq[0] = numbers[0];
    int len = 1;
    vector<int>::iterator end = next(seq.begin());
    for (size_t i = 1; i < numbers.size(); i++) {
        auto pos = std::lower_bound(seq.begin(), end, numbers[i]);
        if (pos == end) {
            *end = numbers[i];
            end = next(end);
            len++;
        }
        else
            *pos = numbers[i];
    }
    return len;
}

さて、これまでのところ良いですが、アルゴリズムが最長(または最長の1つ、ここでは同じサイズのいくつかのサブシーケンス)サブシーケンスの長さを計算することをどのようにして知るのでしょうか。これが私の証拠です:

アルゴリズムが最長のサブシーケンスの長さを計算しないと仮定しましょう。次に、元のシーケンスには、アルゴリズムが失敗し、サブシーケンスが長くなるような数が存在する必要があります。たとえば、サブシーケンスx1、 バツ2、 ...、 バツ xとなるような数yが存在するk <y <xk + 1、1 <= k <= n。サブシーケンスに寄与するには、yはxの間の元のシーケンスに配置する必要がありますk とxk + 1。ただし、矛盾があります。アルゴリズムが元のシーケンスを左から右にトラバースすると、現在のサブシーケンス内の任意の数よりも大きい数に遭遇するたびに、サブシーケンスが1だけ拡張されます。アルゴリズムがそのような数yに出会うまでに、サブシーケンス長さkで、数値xを含みます1、 バツ2、 ...、 バツk。なぜなら、xk <y、アルゴリズムはサブシーケンスを1だけ拡張し、サブシーケンスにyを含めます。 yがサブシーケンスの最小数であり、xの左側にある場合、同じロジックが適用されます。1 または、yがサブシーケンスの最大数であり、xの右側にある場合。結論:そのような数yは存在せず、アルゴリズムは最も長く増加するサブシーケンスを計算します。私はそれが理にかなっていると思います。

最後のステートメントでは、要素を順序付けできるすべてのデータ型について、アルゴリズムを簡単に一般化して最長のサブシーケンスを計算することもできることを述べておきます。考え方は同じですが、コードは次のとおりです。

template<typename T, typename cmp = std::less<T>>
size_t longestSubsequence(const vector<T> &elements)
{
    if (elements.size() < 2)
        return elements.size();
    vector<T>seq(elements.size(), T());
    seq[0] = elements[0];
    size_t len = 1;
    auto end = next(seq.begin());
    for (size_t i = 1; i < elements.size(); i++) {
        auto pos = std::lower_bound(seq.begin(), end, elements[i], cmp());
        if (pos == end) {
            *end = elements[i];
            end = next(end);
            len++;
        }
        else
            *pos = elements[i];
    }
    return len;
}

使用例:

int main()
{
    vector<int> nums = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
    size_t l = longestSubsequence<int>(nums); // l == 6 , longest increasing subsequence

    nums = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
    l = longestSubsequence<int, std::greater<int>>(nums); // l == 5, longest decreasing subsequence

    vector<string> vstr = {"b", "a", "d", "bc", "a"};
    l = longestSubsequence<string>(vstr); // l == 2, increasing


    vstr = { "b", "a", "d", "bc", "a" };
    l = longestSubsequence<string, std::greater<string>>(vstr); // l == 3, decreasing

} 

これはよりコンパクトですが、まだ効率的ですPython実装:

def longest_increasing_subsequence_indices(seq):
    from bisect import bisect_right

    if len(seq) == 0:
        return seq

    # m[j] in iteration i is the last index of the increasing subsequence of seq[:i]
    # that ends with the lowest possible value while having length j
    m = [None] * len(seq)
    predecessor = [None] * len(seq)
    best_len = 0

    for i, item in enumerate(seq):
        j = bisect_right([seq[k] for k in m[:best_len]], item)
        m[j] = i
        predecessor[i] = m[j-1] if j > 0 else None
        best_len = max(best_len, j+1)

    result = []
    i = m[best_len-1]
    while i is not None:
        result.append(i)
        i = predecessor[i]
    result.reverse()
    return result

def longest_increasing_subsequence(seq):
    return [seq[i] for i in longest_increasing_subsequence_indices(seq)]
0
isarandi