最长递增子序列

Posted

技术标签:

【中文标题】最长递增子序列【英文标题】:Longest increasing subsequence 【发布时间】:2011-04-28 21:46:03 【问题描述】:

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

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

[1, 9, 13, 15]  # an example of an increasing subsequence (not the longest)

[0, 2, 6, 9, 13, 15]  # longest increasing subsequence (not a unique answer)
[0, 2, 6, 9, 11, 15]  # another possible solution

我正在寻找最佳算法。如果有代码,Python会很好,但什么都可以。

【问题讨论】:

这里有一些不错的算法:algorithmist.com/wiki/Longest_increasing_subsequence 【参考方案1】:

我只是偶然发现了这个问题,并想出了这个 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

由于我花了一些时间来理解算法是如何工作的,所以我对 cme​​ts 有点冗长,我还将添加一个快速解释:

seq 是输入序列。 L 是一个数字:它在循环遍历序列时得到更新,它标记了到那个时刻发现的最长递增子序列的长度。 M 是一个列表。 M[j-1] 将指向 seq 的索引,该索引包含可用于(最后)构建长度递增的子序列 j 的最小值。 P 是一个列表。 P[i] 将指向M[j],其中iseq 的索引。简而言之,它告诉哪个是子序列的前一个元素。 P 用于最后构建结果。

算法的工作原理:

    处理空序列的特殊情况。 从 1 个元素的子序列开始。 循环遍历索引为i 的输入序列。 通过二分搜索找到j,它让seq[M[j] 成为&lt; 而不是seq[i]。 更新PML。 追溯结果并将其反向返回。

注意:与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之前有4040之前有2050之前有20和之前20 还有10,停下来。

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

此时我们将开始循环seq 并查看10,因为10&lt; 而不是30M 将被更新:

if j == L or seq[i] < seq[M[j]]:
    M[j] = i

所以现在M 看起来像:[1, None, None, ...]。这是一件好事,因为10 有更多的通道来创建更长的递增子序列。 (新的1是10的索引)

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

现在轮到50了。 50 不会成为任何子序列的一部分,因此不会发生任何变化。

现在轮到40了。对于 102040,我们有一个长度为 3 的 sub(M 中的索引 2,所以 M 将是:[1, 2, 4, None, ...]。(新的 4 是 40 的索引)

等等……

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

【讨论】:

啊! Python 好多了,你的 cmets 也有帮助。我会在早上更详细地研究它。 在 python 2.7 中也能很好地工作:) @RikPoggi 你知道我怎么能把它修改为只接受独特的解决方案吗?如果有两个或多个可能的最长子序列,我想同时拒绝它们,而是寻找第二长的子序列,或者第三长的子序列,等等。你可以在这里看到我的问题:***.com/questions/33778471/…【参考方案2】:

以下是如何在 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) 解决方案 观察。令 Ai,j 最小 所有增加的可能尾部 长度为 j 的子序列使用 元素 a1、a2、...、ai。请注意,对于任何 特别是 i, Ai,1, Ai,2, ... , Ai,j。这表明如果 我们想要最长的子序列 以 ai + 1 结尾,我们只需要看 对于一个 j 使得 Ai,j 1、a2、...、an 进行二分搜索。

实现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

【讨论】:

我不明白这是如何满足要求的。你能解释一下吗?【参考方案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'))
'vuuttttttts-s-ronnnnngeee.'

>>> 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 的启发。

【讨论】:

这是一个了不起的答案,也是我喜欢科幻小说的原因!【参考方案4】:

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

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

【讨论】:

【参考方案5】:
    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;

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

【讨论】:

【参考方案8】:

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

我们将使用序列 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]中,而是将元素的索引存储在seq中,因此值为seq[M[x]]

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

它的工作方式是,当处理seq 的元素i 时,我们会找到要添加到哪个序列。请记住,我们要将seq[i] 附加到一个长度为x 的序列上,为一些x 创建一个长度为x+1 的新序列,并且我们将i 存储在M[x+1] 中,而不是seq[i] .稍后,当我们发现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,可以将长度列表j存储在M[j-1]而不是M[j]以避免浪费M[0]的空间,并且可以调用输入序列@ 987654379@ 而不是 seq

【讨论】:

【参考方案9】:
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]

【讨论】:

【参考方案10】:

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

解决这个问题的另一种方法是采用原始数组的longest common subsequence (LCS) 和它的排序版本,这需要 O(N2) 时间。

【讨论】:

实际上,最有效的已知算法在 O(N log log N) 时间内运行(Hunt&Szymanski,“计算最长公共子序列的快速算法”,Communications of the ACM,20(5):350 –353, 1977)。但是,在实践中这不太可能值得打扰。 @FalkHüffner 我认为他在谈论最长的递增子序列而不是最长的公共子序列。【参考方案11】:

这是一个使用“枚举”的紧凑实现

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(N**2) 算法。还有一个错误导致 ValueError: max() arg is an empty sequence 对于某些输入。不适用于 Python 3。【参考方案12】:

这是我的 C++ 解决方案。该解决方案比此处提供的所有解决方案都简单,而且速度很快:N*log(N) 算法时间复杂度。我在 leetcode 提交了解决方案,它运行 4 毫秒,比提交的 100% 的 C++ 解决方案快。

这个想法(在我看来)很明确:从左到右遍历给定的数字数组。维护另外的数字数组(我的代码中的seq),它包含增加的子序列。当取的数大于子序列中所有的数时,放在seq的末尾,子序列长度计数器加1。当数小于子序列迄今为止最大的数时,不管怎样在seq,在它所属的地方通过替换一些现有的数字来保持子序列的排序。子序列使用原始数字数组的长度和初始值 -inf 进行初始化,这意味着给定操作系统中的最小 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;

嗯,到目前为止一切都很好,但是我们怎么知道算法计算了最长(或最长的一个,这里可能是几个相同大小的子序列)子序列的长度?这是我的证明:

假设算法不计算最长子序列的长度。然后在原始序列中必须存在一个数字,这样算法就会丢失并且会使子序列更长。假设对于子序列 x1, x2, ..., xn 存在一个数 y 使得 x kk+1, 1 k 和 xk+1 之间的原始序列中。但是我们有矛盾:当算法从左到右遍历原始序列时,每次遇到比当前子序列中任何数字都大的数字时,它将子序列扩展1。当算法遇到这个数字y时,子序列长度为 k 并包含数字 x1、x2、...、xk。因为 xk1 的左侧或当 y 是子序列的最大编号并且位于 xn。 结论:这样的数字 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

 

【讨论】:

【参考方案13】:

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

我的蟒蛇回答:

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)

常见问题

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

【讨论】:

问题想要找到序列本身,而不是它的长度。

以上是关于最长递增子序列的主要内容,如果未能解决你的问题,请参考以下文章

笔试题1:最长严格递增子序列

O(nlogn)最长递增子序列算法,如何输出所求子序列?

最长递增子序列

算法 LC 动态规划 - 最大递增子序列

[网络流24题] 最长递增子序列

动态规划之最大递增子序列