增长最快的子序列

时间:2010-10-21 23:04:17

标签: python algorithm language-agnostic

给定输入序列,找到最长(不一定是连续的)非递减子序列的最佳方法是什么。

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会很好,但一切都没问题。

13 个答案:

答案 0 :(得分:29)

我偶然发现了这个问题,并提出了这个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. 使用二进制搜索,找到j seq[M[j] <而不是seq[i]
  5. 更新PML
  6. 追溯结果并将其反转。
  7. 注意:wikipedia algorithm的唯一区别是M列表中的偏移量为1,X在此处称为seq }。我也用Eric Gustavson answer中显示的单元测试版略有改进测试它,它通过了所有测试。


    示例:

    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, ...]之前M,停止。

    复杂的部分在30。在开头seq10,因为长度为1的子序列的最后一个元素(因此10中的位置0)位于索引0:<

    此时我们将开始在30上循环并查看M,因为if j == L or seq[i] < seq[M[j]]: M[j] = i M而不是[1, None, None, ...]10将更新:

    20

    现在10看起来像是:20。这是一件好事,因为M有更多的通道来创建一个更长的增加子序列。 (新的1是10的索引)

    现在轮到M了。对于[1, 2, None, ...]50,我们有长度为2的子序列(50中的索引1),因此40将为:10。 (新的2是20的索引)

    现在轮到20了。 40不会成为任何子序列的一部分,所以没有任何改变。

    现在轮到M了。对于M[1, 2, 4, None, ...]和{{1}},我们有一个长度为3的子点({{1}}中的索引2,因此{{1}}将为:{{1}}。 (新4是指数40)

    等等......

    要完整浏览代码,您可以复制并粘贴代码here:)

答案 1 :(得分:8)

以下是如何在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)解决方案   观察结果。让艾,j成为最小的   所有增加的可能尾巴   使用长度为j的子序列   元素a 1 2 ,..., i 。观察,对任何人   特别是i,A i,1 ,A i,2 ,...,A i,j 。这表明如果   我们想要最长的子序列   以ai + 1结尾,我们只需要看   对于j,使得Ai,j <1。 ai + 1&lt; =   Ai,j + 1,长度为j + 1。   请注意,在这种情况下,Ai + 1,j + 1   将等于ai + 1,并且所有Ai +   1,k等于Ai,k等于k!= j + 1。   此外,最多只有一个   集合Ai和之间的区别   设置Ai + 1,这是由此引起的   搜索。因为A总是被订购   增加秩序和操作   我们可以不改变这种顺序   对每个 1 2 ,..., n 进行二元搜索。

     

实施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中的矢量替代是List。但是如果你想自己测试一下,这里是在线编译器的链接,加载了示例实现:link

示例数据是:{ 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 } 并回答:1 3 4 5 6 7

答案 2 :(得分:3)

以下是一个非常通用的解决方案:

  • 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')]

这个答案的部分灵感来自question over at Code Review,部分来自question asking about "out of sequence" values

答案 3 :(得分:2)

这是一些带有测试的python代码,它实现了在O(n * log(n))中运行的算法。我在关于wikipedia talk pagelongest increasing subsequence上找到了此信息。

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()

答案 4 :(得分:1)

    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;
}

答案 5 :(得分:1)

这是Java的代码和解释,可能我很快就会为python添加。

arr = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
  1. list = {0} - 将列表初始化为空集
  2. list = {0,8} - 新的最大LIS
  3. list = {0,4} - 更改了8到4
  4. list = {0,4,12} - 新的最大LIS
  5. list = {0,2,12} - 更改了4到2
  6. list = {0,2,10} - 更改了12到10
  7. list = {0,2,6} - 将10改为6
  8. list = {0,2,6,14} - 新的最大LIS
  9. list = {0,1,6,14} - 更改为2 <1
  10. list = {0,1,6,9} - 更改了14到9
  11. list = {0,1,5,9} - 更改了6到5
  12. list = {0,1,6,9,13} - 更改为3 <2
  13. list = {0,1,3,9,11} - 新的最大LIS
  14. list = {0,1,3,9,11} - 更改了9到5
  15. list = {0,1,3,7,11} - 新的最大LIS
  16. list = {0,1,3,7,11,15} - 新的最大LIS
  17. 所以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]

答案 6 :(得分:0)

最有效的算法是O(NlogN)概述here

解决此问题的另一种方法是采用原始数组的longest common subsequence(LCS)及其排序版本,这需要O(N 2 )时间。

答案 7 :(得分:0)

这是使用&#34;枚举&#34;

的紧凑实现
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)

答案 8 :(得分:0)

这是一个更紧凑但仍然有效的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)]

答案 9 :(得分:0)

在代码中有几个答案,但是我发现它们有点难以理解,因此这里是对总体思想的解释,其中省略了所有优化。稍后我会进行优化。

我们将使用序列2、8、4、12、3、10,并且为了使其易于理解,我们将要求输入序列不能为空并且不能多次包含相同的数字。

我们按顺序进行操作。

这样做,我们将维护一组序列,这是迄今为止针对每种长度找到的最佳序列。在找到长度为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的序列(将3替换为4),因为它们在输入序列中并未按该顺序出现。下一个元素10将解决此问题。因为我们能做的最好的10就是将其添加到2 3上,因此它成为长度为3的新列表:

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

请注意,就算法而言,我们真的不在乎任何候选序列中最后一个元素之前的内容,但是我们当然需要保持跟踪,以便最后可以输出完整序列。

我们继续像这样处理输入元素:只需将每个元素固定在我们可以做到的最长序列上,并为所得长度创建新的候选序列,因为可以保证它不会比该长度的现有序列差。最后,我们输出找到的最长序列。

优化

一种优化是,我们实际上不需要存储每个长度的整个序列。这样做将占用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]中,而是将元素的索引存储在{{1}中},因此值为seq

我们这样做是为了通过链接子序列来记录整个序列。如开始时所见,每个序列都是通过在现有序列的末尾添加一个元素来创建的。因此,,我们保留了另一个数组seq[M[x]],用于存储要添加到的序列的最后一个元素的索引(在P中)。为了使其可链接,由于我们存储在seq中的内容是一个P的索引,因此我们必须以seq的索引来索引P本身。

这种工作方式是,在处理seq的元素i时,我们发现要添加到哪个序列上。记住,我们将seq固定在长度为seq[i]的序列上,以为某些x创建一个长度为x+1的新序列,并且我们将存储{{1} },而不是x中的i。后来,当我们发现seq[i]是可能的最大长度时,我们将要重建序列,但是唯一的起点是M[x+1]

我们要做的是设置x+1M[x+1](与M[x+1] = i相同),也就是说,对于我们添加的每个元素P[i] = M[x],我们存储{ {1}}是最长链中的最后一个元素,并且我们在P[M[x+1]] = M[x]中存储要扩展的链中最后一个元素的索引。所以我们有:

i

现在我们完成了。如果要将其与实际代码进行比较,可以查看other examples。主要区别在于它们使用i而不是P[i],可以将长度为last element: seq[M[x]] before that: seq[P[M[x]]] before that: seq[P[P[M[x]]]] etc... 的列表存储在j而不是x处,以避免浪费{ {1}},并且可以调用输入序列j而不是M[j-1]

答案 10 :(得分:0)

这是我的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;
}

到目前为止,还算不错,但是我们怎么知道该算法计算出最长(或最长的一个,这里可能是几个相同大小的子序列)子序列的长度?这是我的证明:

让我们假设该算法不计算最长子序列的长度。然后,在原始序列中必须存在一个数字,使得算法遗漏了该序列,这会使子序列变长。假设,对于子序列x 1 ,x 2 ,...,x n ,存在一个y,使得x k k + 1 ,1 <= k <= n。为了有助于子序列,y必须位于x k 和x k + 1 之间的原始序列中。但是,我们有一个矛盾:当算法从左到右遍历原始序列时,每次遇到一个比当前子序列中的任何数字大的数字时,它都会将该子序列扩展1。到了该算法满足该数字y的子序列时长度为k,包含数字x 1 ,x 2 ,...,x k 。由于x k 1 的左侧时,或者当y是子序列的最大数量并且位于x n 的右侧时,适用相同的逻辑。 / sub>。 结论:这样的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

} 

答案 11 :(得分:0)

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]

答案 12 :(得分:0)

其他解决方案的冗长和复杂性让我感到不安。

我的python答案:

def findLIS(s):
  lengths = [1] * len(s)
  for i in range(1, len(s)):
    for j in range(i):
      if s[i] > s[j] and lengths[i] <= lengths[j]:
        lengths[i] += 1
  return max(lengths)

常见问题解答

  1. 我们初始化lengths列表[1, 1, 1, ..., 1],因为最坏的情况是长度1:[5,4,3,2]的结果长度为[1,1,1,1],我们可以取最大的长度,即1。
  2. 算法:对于每个数字,我们尝试查看此新数字是否可以使子序列更长。 最重要的部分是if s[i] > s[j] and lengths[i] <= lengths[j]:我们确保此新数字更大,并且其最佳子序列为不再更长。如果是这样,这是一个很好的数字,可以添加到旧的子序列中。
  3. 我的答案实际上得到的是递增子序列的长度(问题的标题),实际上与不减少的长度(问题的描述)不同。如果要获得最长的不减少子序列长度,只需将s[i] > s[j]更改为s[i] >= s[j]