找到增长最快的序列

时间:2011-02-08 21:44:14

标签: algorithm lcs

您将获得一系列数字,您需要从给定输入中找到最长的增加子序列(不必连续)。

我找到了这个链接(Longest increasing subsequence on Wikipedia)但需要更多解释。

如果有人能帮我理解O(n log n)实现,那将非常有用。如果你能用一个例子解释算法,那将非常感激。

我也看到了其他帖子,我不明白的是: L = 0  对于i = 1,2,... n:    二进制搜索最大正j≤L,使得X [M [j]] < X [i](如果不存在这样的值,则设置j = 0)  上面的陈述,从哪里开始二元搜索?如何初始化M [],X []?

7 个答案:

答案 0 :(得分:98)

一个更简单的问题是找到最长的增长子序列的长度。您可以首先专注于理解该问题。该算法的唯一区别是它不使用 P 数组。

x 是序列的输入,因此可以初始化为: x = [0,8,4,12,2,10,6,14,1,9,5,13,​​3,11,7,15]

m 跟踪到目前为止找到的每个长度的最佳子序列。最好的是具有最小结束值的那个(允许在其后添加更宽范围的值)。长度和结束值是每个子序列需要存储的唯一数据。

m 的每个元素代表一个子序列。对于 m [j]

  • j 是子序列的长度。
  • m [j] 是子序列最后一个元素的索引(在 x 中)。
  • 所以, x [m [j]] 是子序列的最后一个元素的值。

L 是迄今为止发现的最长子序列的长度。 m 的第一个 L 值有效,其余的未初始化。 m 可以从第一个元素为0开始,其余元素未初始化。算法运行时 L 会增加, m 的初始化值也会增加。

这是一个示例运行。给出了每次迭代结束时的 x [i] m (但是使用了序列的值而不是索引)。

每次迭代中的搜索都在寻找放置 x [i] 的位置。它应尽可能向右(获得最长的序列),并且大于其左边的值(因此它是一个递增的序列)。

 0:  m = [0, 0]        - ([0] is a subsequence of length 1.)
 8:  m = [0, 0, 8]     - (8 can be added after [0] to get a sequence of length 2.)
 4:  m = [0, 0, 4]     - (4 is better than 8. This can be added after [0] instead.)
 12: m = [0, 0, 4, 12] - (12 can be added after [...4])
 2:  m = [0, 0, 2, 12] - (2 can be added after [0] instead of 4.)
 10: m = [0, 0, 2, 10]
 6:  m = [0, 0, 2, 6]
 14: m = [0, 0, 2, 6, 14]
 1:  m = [0, 0, 1, 6, 14]
 9:  m = [0, 0, 1, 6, 9]
 5:  m = [0, 0, 1, 5, 9]
 13: m = [0, 0, 1, 5, 9, 13]
 3:  m = [0, 0, 1, 3, 9, 13]
 11: m = [0, 0, 1, 3, 9, 11]
 7:  m = [0, 0, 1, 3, 7, 11]
 15: m = [0, 0, 1, 3, 7, 11, 15]

现在我们知道有一个长度为6的子序列,以15结尾。子序列中的实际值可以通过在循环期间将它们存储在 P 数组中找到。

检索最佳子序列:

P 将前一个元素存储在每个数字的最长子序列(作为x的索引)中,并随着算法的进展而更新。例如,当我们处理8时,我们知道它在0之后,因此在 P 中存储8在0之后的事实。您可以从最后一个数字向后工作,如链接列表,以获得整个序列。

因此,对于每个数字,我们都知道它之前的数字。要查找以7结尾的子序列,我们查看 P 并查看:

7 is after 3
3 is after 1
1 is after 0

所以我们有子序列[0,1,3,7]。

以7或15结尾的子序列共享一些数字:

15 is after 11
11 is after 9
9 is after 6
6 is after 2
2 is after 0

所以我们有子序列[0,2,6,9,11]和[0,2,6,9,11,15](增长最长的子序列)

答案 1 :(得分:4)

麻省理工学院网站给出了这个问题的最佳解释之一。 http://people.csail.mit.edu/bdean/6.046/dp/

我希望它能清除你所有的疑虑。

答案 2 :(得分:1)

基于FJB的回答,java实现:

public class Lis {

private static int[] findLis(int[] arr) {
    int[] is = new int[arr.length];
    int index = 0;
    is[0] = index;

    for (int i = 1; i < arr.length; i++) {
        if (arr[i] < arr[is[index]]) {
            for (int j = 0; j <= index; j++) {
                if (arr[i] < arr[is[j]]) {
                    is[j] = i;
                    break;
                }
            }
        } else if (arr[i] == arr[is[index]]) {

        } else {
            is[++index] = i;
        }
    }

    int[] lis = new int[index + 1];
    lis[index] = arr[is[index]];

    for (int i = index - 1; i >= 0; i--) {
        if (is[i] < is[i + 1]) {
            lis[i] = arr[is[i]];
        } else {
            for (int j = is[i + 1] - 1; j >= 0; j--) {
                if (arr[j] > arr[is[i]] && arr[j] < arr[is[i + 1]]) {
                    lis[i] = arr[j];
                    is[i] = j;
                    break;
                }
            }
        }
    }

    return lis;
}

public static void main(String[] args) {
    int[] arr = new int[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11,
            7, 15 };
    for (int i : findLis(arr)) {
        System.out.print(i + "-");
    }
    System.out.println();

    arr = new int[] { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 };
    for (int i : findLis(arr)) {
        System.out.print(i + "-");
    }
    System.out.println();
}

}

答案 3 :(得分:1)

以下是O(NLogN)增长最长的子序列实现:

// search for the index which can be replaced by the X. as the index can't be
//0 or end (because if 0 then replace in the findLIS() and if it's greater than the 
//current maximum the just append)of the array "result" so most of the boundary 
//conditions are not required.
public static int search(int[] result, int p, int r, int x)
{
    if(p > r) return -1;
    int q = (p+r)/2;
    if(result[q] < x && result[q+1]>x)
    {
        return q+1;
    }
    else if(result[q] > x)
    {
        return search(result, p, q, x);
    }
    else
    {
        return search(result, q+1, r, x);
    }
}
    public static int findLIS(int[] a)
    {
        int[] result = new int[a.length];
        result[0] = a[0];
        int index = 0;
        for(int i=1; i<a.length; i++)
        {
            int no = a[i];
            if(no < result[0]) // replacing the min number
            {
                result[0] = no;
            }
            else if(no > result[index])//if the number is bigger then the current big then append
            {
                result[++index] = no;
            }
            else
            {
                int c = search(result, 0, index, no);
                result[c] = no;
            }
        }
        return index+1;
    }

答案 4 :(得分:0)

基于@fgb的回答,我使用c ++实现了算法,以找到最长的严格增加的子序列。希望这会有所帮助。

M [i]是长度为i的序列的最后一个元素的索引,P [i]是序列中i的前一个元素的索引,用于打印整个序列。

main()用于运行简单的测试用例:{0,8,4,12,2,10,6,14,1,9,5,13,​​3,11,7,15}。

#include <vector>
using std::vector;
int LIS(const vector<int> &v) {
  int size = v.size(), max_len = 1;
  // M[i] is the index of the last element of the sequence whose length is i
  int *M = new int[size];
  // P[i] is the index of the previous element of i in the sequence, which is used to print the whole sequence
  int *P = new int[size];
  M[0] = 0; P[0] = -1;
  for (int i = 1; i < size; ++i) {
    if (v[i] > v[M[max_len - 1]]) {
      M[max_len] = i;
      P[i] = M[max_len - 1];
      ++max_len;
      continue;
    }
    // Find the position to insert i using binary search
    int lo = 0, hi = max_len - 1;
    while (lo <= hi) {
      int mid = lo + ((hi - lo) >> 1);
      if (v[i] < v[M[mid]]) {
        hi = mid - 1;
      } else if (v[i] > v[M[mid]]) {
        lo = mid + 1;
      } else {
        lo = mid;
        break;
      }
    }
    P[i] = P[M[lo]];  // Modify the previous pointer
    M[lo] = i;  
  }
  // Print the whole subsequence
  int i = M[max_len - 1];
  while (i >= 0) {
    printf("%d ", v[i]);
    i = P[i];
  }
  printf("\n");
  delete[] M, delete[] P;
  return max_len;
}
int main(int argc, char* argv[]) {
  int data[] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
  vector<int> v;
  v.insert(v.end(), data, data + sizeof(data) / sizeof(int));
  LIS(v);
  return 0;
}

答案 5 :(得分:0)

晚会,但这是一个JavaScript实现与其他人一起.. :))

var findLongestSubsequence = function(array) {
  var longestPartialSubsequences = [];
  var longestSubsequenceOverAll = [];

  for (var i = 0; i < array.length; i++) {
    var valueAtI = array[i];
    var subsequenceEndingAtI = [];

    for (var j = 0; j < i; j++) {
      var subsequenceEndingAtJ = longestPartialSubsequences[j];
      var valueAtJ = array[j];

      if (valueAtJ < valueAtI && subsequenceEndingAtJ.length > subsequenceEndingAtI.length) {
        subsequenceEndingAtI = subsequenceEndingAtJ;
      }
    }

    longestPartialSubsequences[i] = subsequenceEndingAtI.concat();
    longestPartialSubsequences[i].push(valueAtI);

    if (longestPartialSubsequences[i].length > longestSubsequenceOverAll.length) {
      longestSubsequenceOverAll = longestPartialSubsequences[i];
    }
  }

  return longestSubsequenceOverAll;
};

答案 6 :(得分:-7)

我们可以在两个2d数组中实现它 像序列一样

 8    2     4
 0    7     1
 3    7     9

且LIS为0 - > 2 - &gt; 4 - &gt; 7 - &gt; 8  什么是这个

的算法