入力シーケンスが与えられた場合、最も長い(必ずしも連続的ではない)非減少サブシーケンスを見つけるための最良の方法は何ですか。
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=でいいでしょうが、何でもかまいません。
私はこの問題に遭遇し、これを思いついた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]
_を指します。ここで、i
はseq
のインデックスです。一言で言えば、それはサブシーケンスの前の要素がどれであるかを示します。 P
は、最後に結果を作成するために使用されます。アルゴリズムのしくみ:
i
の入力シーケンスをループします。seq[M[j]
_を_<
_より_seq[i]
_にするj
を見つけます。P
、M
およびL
を更新します。注: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 :)
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
。
これはかなり一般的な解決策です:
O(n log n)
時間で実行され、list
、numpy.array
、str
などを含むすべてのシーケンスオブジェクトで機能します。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での質問 に触発され、一部は 「シーケンス外」の値に関する質問 に触発されました。
ここにいくつかの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()
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;
}
Javaでのコードと説明は次のとおりです。pythonを間もなく追加する予定です。
arr = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
したがって、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]
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]
コードにはいくつかの答えがありますが、私はそれらを理解するのが少し難しいと感じたので、ここでは、すべての最適化を省いて、一般的な考え方を説明します。最適化については後で説明します。
シーケンス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
の新しいシーケンスを作成し、i
、seq[i]
のM[x+1]
ではありません。後で、x+1
が可能な最大の長さであることがわかったときに、シーケンスを再構築する必要がありますが、開始点はM[x+1]
のみです。
M[x+1] = i
とP[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
を使用することであり、長さのリストj
をM[j-1]
ではなくM[j]
に格納して、スペースはM[0]
にあり、X
ではなく入力シーケンスseq
を呼び出すことがあります。
これは、「列挙」を使用したコンパクトな実装です
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)
このための最も効率的なアルゴリズムは、O(NlogN)概説 ここ です。
これを解決する別の方法は、元の配列の longest common subsequence (LCS)を使用することです。これは、O(N2)時間。
これが問題の私のC++ソリューションです。解決策は、これまでに提供されたすべてのものよりも単純で、高速です:N*log(N)
アルゴリズムの時間の複雑さ。 leetcodeでソリューションを提出しました。4ミリ秒で実行され、提出されたC++ソリューションの100%よりも高速です。
考えは(私の意見では)明確です。指定された数値の配列を左から右にトラバースします。追加のシーケンスを保持する数値の配列(コードでは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)]