最长递增子序列(LIS)

来源:互联网 发布:淘宝卖家双11报名入口 编辑:程序博客网 时间:2024/05/17 09:35

上个月去大众点评面试,面试官让我写LIS,尼玛我当时竟然听成了求“子数组之和的最大值”,写好后被面试官指了出来,我又想当然以为是”子数组“,再次写好后,面试官看了看,也没说什么,我还以为有戏。结果第二天在去趋势面试的路上,收到了拒信,秋风那个吹啊……尴尬

直到昨晚看了《编程之美》的2.16,才搞明白”子数组“和”子序列“的区别,晚上躺在床上用我的弱鸡鸭看到了这篇文章http://www.ahathinking.com/archives/117.html,基本明白了原理,今天稍微整理下以备后用。

三种方法:

1. 动态规划

以array[i]为最大元素的最长递增子序列的长度记为LIS[i],显然LIS[i] >= 1,array[i]元素本身至少可提供长度为1的递增子序列。对于array[i],从左到右依次考虑array[i]之前的元素,array[k] (0<=k<i),若array[k]<array[i],且LIS[k]+1 > LIS[i],则用LIS[k]+1来更新LIS[i]。最终可得到最长递增子序列的长度为lisLength。如数组”1 -1 2 -3 4 -5 6 -7“对应的LIS信息为:

数组:     1 -1 2 -3 4 -5 6 -7

LIS记录:1  1 2  1  3  1 4  1

这种方法有一个好处是可以打印出最长递增子序列,从i=array.size()-1开始向前遍历,若LIS[i] == lisLength,则array[i]入栈(或者递归),同时--i,--lisLength;否则--i(向前遍历);当lisLength == 0时终止,打印出最长递增子序列。

具体代码如下:

#include <iostream>#include <vector>#include <sstream>using namespace std;int GetLIS(const vector<int> &data, vector<int> &record){    vector<int>::size_type currPos=1;    int maxLength = 1;    for (; currPos<data.size(); ++currPos)    {        for (vector<int>::size_type prevPos=0; prevPos<currPos; ++prevPos)        {            if (data[prevPos] < data[currPos])            {                if (record[prevPos]+1 > record[currPos])                {                    record[currPos] = record[prevPos] + 1;                }            }        }        if (record[currPos] > maxLength)        {            maxLength = record[currPos];        }    }    return maxLength;}void PrintLIS(const vector<int> &data, const vector<int> &record, int currPos, int lisLength){    if (lisLength == 0)    {        return;    }    while (record[currPos] != lisLength)    {        --currPos;    }    PrintLIS(data, record, currPos-1, lisLength-1);    cout << data[currPos] << ' ';}void GetLIS(const vector<int> &data){    if (data.empty())    {        return;    }    vector<int> record(data.size(), 1);    int lisLength = GetLIS(data, record);    cout << lisLength << endl;    PrintLIS(data, record, data.size()-1, lisLength);    cout << endl;}int main(){    istringstream input("1 -1 2 -3 4 -5 6 -7");    vector<int> data;    int iTmp;    while (input >> iTmp)    {        data.push_back(iTmp);    }    GetLIS(data);    return 0;}


2. 动态规划+快速排序

这个我没有亲自实践,看参考文章说是将原数组利用QuickSort生成一个排好序的副本,然后求原数组与对应的有序数组的最长公共子序列(LCS)即可。


3. 动态规划+二分查找(吊炸天的方法)

用minLast[i]表示长度为(i+1)的递增子序列(可能有多个)的最大元素(最后一个元素)的最小值,如数组”1 -1 2 -3 4 -5 6 -7“对应的minLast为:minLast[0] = -7,minLast[1] = 2,minLast[2] = 4,minLast[3] = 6,此时不再需要LIS数组。

需要注意的是,对于i < j,必有minLast[i] < minLast[j],否则必可以从小于minLast[j]的元素中找到一个数来取代minLast[i]。根据这个关系便可以执行二分查找,找到对应的更新位置。对于当前元素array[currPos],如果其值大于minLast.back(),则需要更新lisLength,即push_back(array[currPos]);否则利用二分查找找到刚好大于array[currPos]的minLast[updatePos],然后用array[currPos]的值来更新minLast[updatePos],因为在array[currPos]之前的元素中可以找到(updatePos-1)个元素与array[currPos]构成长度为updatePos的递增子序列,且以array[currPos]为最大(最后一个)元素。(对于array[currPos] == minLast[updatePos]的情形,其实并不会造成任何实质上的minLast更新)

具体代码如下:

#include <iostream>#include <sstream>#include <vector>using namespace std;int BinarySearch(const vector<int> &minLast, int value){    int left = 0;    int right = minLast.size() - 1;    while (left <= right)    {        int mid = (left+right) / 2;        if (minLast[mid] == value)        {            return mid;        }        else if (minLast[mid] < value)        {            left = mid + 1;        }        else        {            right = mid - 1;        }    }    return left;}int GetLIS(const vector<int> &data){    int lisLength = 0;    vector<int> minLast;    vector<int>::size_type pos = 0;    for (; pos<data.size(); ++pos)    {        if (lisLength==0 || data[pos]>minLast.back())        {            minLast.push_back(data[pos]);            ++lisLength;        }        else        {            int updatePos = BinarySearch(minLast, data[pos]);            minLast[updatePos] = data[pos];        }    }/*    for (int i=0; i<minLast.size(); ++i)    {        cout << minLast[i] << ' ';    }    cout << endl;*/    return lisLength;}int main(){    istringstream input("1 -1 2 -3 4 -5 6 -7");    vector<int> data;    int iTmp;    while (input >> iTmp)    {        data.push_back(iTmp);    }    cout << GetLIS(data) << endl;    return 0;}

原创粉丝点击