Longest Increasing Subsequence

来源:互联网 发布:亚马逊windows 编辑:程序博客网 时间:2024/06/03 20:52

Longest Increasing Subsequence

Longest Increasing Subsequence问题是找到给定序列的子序列,其中子序列的元素按照排序顺序排列,从最低到最高,并且子序列尽可能长。这个子序列不一定是连续的,也不是唯一的。

例如,考虑子序列{0,8,4,12,2,10,6,14,1,9,5,11,13,3,11,7,15}

最长增长的子序列是{0,2,6,9,11,15}

这个序列长度为6;输入序列没有7个成员增加的子序列。这个例子中Longest Increasing Subsequence不是唯一的:例如,

    {0,4,6,9,11,15}或    {0,4,6,9,13,15}

是相同输入序列中长度相等的其他增加的子序列。

我们已经讨论了使用动态规划的LIS的O(n2)时间复杂度解决方案。在这篇文章中,讨论了一个O(nlogn)时间非DP解决方案。

令S [i]定义为结束长度为i的递增序列的最小整数。现在遍历输入集合的每个整数X,并执行以下操作:

如果X超过S中的最后一个元素,则将X附加到S的末尾。这实际上意味着我们已经找到了一个新的最大的LIS。

否则在S中找到最小的元素,它大于等于X,并将其替换为X.由于S随时进行排序,所以可以使用log(N)时间内的二进制搜索找到该元素。

我们借助一个例子来说明这一点。考虑下面的整数数组

 {2,6,3,4,1,2,9,5,8}

以下是算法遵循的步骤 -

初始化为空集S = {}插入2 - S = {2} - 最大LIS插入6 - S = {2,6} - 新的最大LIS插入3 - S = {2,3} - 用3替换6插入4-S = {2,3,4} - 新的最大LIS插入1 - S = {1,3,4} - 用1替换2插入2 - S = {1,2,4} - 用2替换3插入9 - S = {1,2,4,9} - 新的最大LIS插入5 - S = {1,2,4,5} - 用5替换9插入8 - S = {1,2,4,5,8} - 最大的LIS

那么LIS的长度是5(S的大小)。请注意,这里S [i]被定义为结束长度为i的递增序列的最小整数。因此,S不代表实际的序列,而S的大小代表LIS的长度。

以下解决方案使用std :: set实现为红黑二进制搜索树,其具有用于插入的最坏情况时间复杂度为O(logn)。

C ++实现 -

#include <bits/stdc++.h>using namespace std;//给定数组中查找长度最长的增加子序列的函数int findLISLength(int arr[], int n){      //创建一个空的有序集S.在S中的第i个元素被定义为//结束长度为i的递增序列的最小整数    set<int> S;      //逐个处理每个元素    for (int i = 0; i < n; i++)    {       //将当前元素插入到集合中        auto ret = S.insert(arr[i]);        //获取迭代器插入元素        set<int>::iterator it;        if (ret.second)            it = ret.first;         //如果元素没有插入到最后,然后删除下一个        //更大的元素从集合        if (++it != S.end())            S.erase(it);    }      // LIS的长度是集合中的元素数      return S.size();}// 主功能int main(){    int arr[] = { 2, 6, 3, 4, 1, 2, 9, 5, 8 };    int n = sizeof(arr) / sizeof(arr[0]);    cout << "Length of LIS is " << findLISLength(arr, n) << endl;    return 0;}
Output:
Length of LIS is 5

如何打印LIS?

为了使事情变得更简单,我们可以将S中的索引保留在S中,而不是实际的整数。那就是我们不保留{1,2,4,5,8},而是保留{4,5,3,7,8},因为arr [4] = 1,arr [5] = 2,arr [3] = 4,arr [7] = 5,arr [8] = 8。

要重建实际的LIS,我们必须使用一个父数组。让父母[i]成为LIS中索引i的元素的前身,以索引i的元素结尾。如果我们正确地更新了父数组,实际的LIS是:
ARR [S [lastElementOfS]]ARR [父[S [lastElementOfS]]],ARR [父[父[S [lastElementOfS]]]],

下面的解决方案将实际整数和它们的索引存储在集合中以便于实现 -

#include <bits/stdc++.h>using namespace std;//数据结构存储元素及其索引在数组中struct Node{    int elem;    int index;};//重载比较运算符插入到集合中inline bool operator<(const Node& lhs, const Node& rhs){    return lhs.elem < rhs.elem;}//使用父数组打印LIS的功能void print(int input[], auto parent, set<Node> S){     //容器以相反的顺序存储LIS    stack<int> lis;      //从S的最后一个元素开始    int index = S.rbegin()->index;    //获取LIS的长度    int n = S.size();    // retrieve LIS from parent array    while (n--)    {        lis.push(input[index]);        index = parent[index];    }     //打印LIS    cout << "LIS is ";    while(!lis.empty())    {        cout << lis.top() << " ";        lis.pop();    }}//给定数组中查找最长递增子序列的函数void printLIS(int arr[], int n){   //创建一个空的有序集合S(S中的第i个元素被定义为    //结束长度增加序列的最小整数i)    set<Node> S;    // parent [i]将存储索引i的元素的前身    // LIS以索引i的元素结尾    map<int, int> parent;    //逐个处理每个元素    for (int i = 0; i < n; i++)    {          //从当前元素及其索引构造节点        Node curr = {arr[i], i};         //将当前节点插入到集合中并获取迭代器        //插入节点        auto it = S.insert(curr).first;        //如果最后没有插入节点,则删除下一个节点        if (++it != S.end())            S.erase(it);         //获取迭代器到当前节点并更新父节点        it = S.find(curr);        parent[i] = (--it)->index;    }      //使用父地图打印LIS    print(arr, parent, S);}// 主功能int main(){    int arr[] = { 2, 6, 3, 4, 1, 2, 9, 5, 8 };    int n = sizeof(arr) / sizeof(arr[0]);    printLIS(arr, n);    return 0;}
Output:
LIS is 2 3 4 5 8

上述解的时间复杂度为O(nlog(n)),程序使用的辅助空间为O(n)。

原创粉丝点击