《编程之美》——求数组的最长递增子序列

问题:
求数组最长递增子序列。

分析与解法:
【解法一】
对数组进行排序,将有序的数组与原数组一起求最长公共子序列,于是原问题可以转化为求最长公共子序列的问题。排序的时间复杂度为O(nlogn),求最长公共子序列的问题时间复杂度为O(n^2),总的时间复杂度为O(n^2)

【解法二】
使用动态规划的方法,因为要求长度为i的序列的Ai{a1,a2,……,ai}最长递增子序列,需要先求出序列Ai-1{a1,a2,……,ai-1}中以各元素(a1,a2,……,ai-1)作为最大元素的最长递增序列,然后把所有这些递增序列与ai比较,如果某个长度为m序列的末尾元素aj(j小于i)比ai要小,则将元素ai加入这个递增子序列,得到一个新的长度为m+1的新序列,否则其长度不变,将处理后的所有i个序列的长度进行比较,其中最长的序列就是所求的最长递增子序列。时间复杂度为O(n^2)

设在目标数组arr[]的前i个元素中,最长递增子序列的长度为LIS[i],则可以得到递推关系式:

LIS[i+1]=max{1, LIS[j]+1},arr[i+1]>arr[j],j <= i

即若arr[i]大于arr[j],那么arr[i]可以接在LIS[j]后面构成一个更长的子序列,同时其本身也可以构成一个长度为1的子序列。

举例说明,设目标数组为arr[8] = {1, -1, 2, -3, 4, -5, 6, -7},整个过程为:
i = 0是1
i = 1是-1
i = 2是1, 2和-1, 2
i = 3是-3
i = 4是1, 2, 4和-1, 2, 4
i = 5是-5
i = 6是1, 2, 4, 6和-1, 2, 4, 6
i = 7是-7

代码:

#include <iostream>
#define MAXLENGTH 1000
using namespace std;

/* arr[] 目标数组 n 目标数组长度 LIS[i]在目标数组arr[]的前i个元素中,最长递增子序列的长度 s[] 最长子序列 */

int LongestIncreasingSubsequence(int arr[], int n, int LIS[], int s[])
{
    int path[MAXLENGTH];
    for(int i = 0; i < n; i++)
    {
        //记录最长子序列中元素的前后关系,方便输出
        path[i] = i;
    }
    LIS[0] = 1;
    for(int i = 1; i < n; i++)
    {
        LIS[i] = 1;
        for(int j = 0; j < i; j++)
        {
            //如果要求非递减子序列,改为arr[j] <= arr[i]
            //如果要求递减子序列,改为arr[j] > arr[i]
            if(arr[j] < arr[i] && LIS[j] + 1 > LIS[i])
            {
                //更新LIS数组
                LIS[i] = LIS[j] + 1;
                //将arr[i]在最长子序列中的前一个元素存入path数组中
                path[i] = j;
            }
        }
    }
    int mLen = 0;
    int end = -1;
    for(int i = 0; i < n; i++)
    {
        if(LIS[i] > mlen)
        {
            //最长子序列的长度
            mLen = LIS[i];
            //获取最长子序列中最后一个元素的下标
            end = i;
        }
    }
    int i = 1;
    s[0] = arr[end];
    while(path[end] != end)
    {
        //将最长子序列倒序存入s数组,输出时从后往前输出
        s[i++] = arr[path[end]];
        end = path[end];
    }
    return mLen;//最长子序列的长度
}

int main()
{
    int n;
    int arr[MAXLENGTH];
    int LIS[MAXLENGTH];
    int s[MAXLENGTH];
    while (cin >> n, n != 0)
    {
        for (int i = 0; i < n; ++i)
        {
            cin >> arr[i];
        }
        int mLen = LongestIncreasingSubsequence(arr, n, LIS, s);
        cout << "Longest Increasing Subsequence's Length: " << mLen << endl;
        for (int i = mLen - 1; i >= 0; i--)
        {
            cout << s[i];//倒序遍历s,以arr的正序输出
        }
        cout << endl;
    }
}

初始化 LIS[8]={1,1,1,1,1,1,1,1},
i = 0是LIS[8]={1,1,1,1,1,1,1,1},
i = 1是LIS[8]={1,1,1,1,1,1,1,1},
i = 2是LIS[8]={1,1,2,1,1,1,1,1},
i = 3是LIS[8]={1,1,2,1,1,1,1,1},
i = 4是LIS[8]={1,1,2,1,3,1,1,1},
i = 5是LIS[8]={1,1,2,1,3,1,1,1},
i = 6是LIS[8]={1,1,2,1,3,1,4,1},
i = 7是LIS[8]={1,1,2,1,3,1,4,1}

初始化 path[8]={0,1,2,3,4,5,6,7},
i = 0是path[8]={0,1,2,3,4,5,6,7},
i = 1是path[8]={0,1,2,3,4,5,6,7},
i = 2是path[8]={0,1,1,3,4,5,6,7},
i = 3是path[8]={0,1,1,3,4,5,6,7},
i = 4是path[8]={0,1,1,3,2,5,6,7},
i = 5是path[8]={0,1,1,3,2,5,6,7},
i = 6是path[8]={0,1,1,3,2,5,4,7},
i = 7是path[8]={0,1,1,3,2,5,4,7}

【解法三】
令maxV[i]代表:长度为i的单调递增子序列中最后一个元素的最小值。当一个新元素进来,生成新的长度为i的单调递增子序列,且新的子序列中最后一个元素的值小于之前的子序列中最后一个元素的值,则更新maxV[i],从而保证maxV[i]尽可能的小。

举例说明,以序列{6,7,8,9,10,1,2,3,4,5,6}来说明算法的步骤:
程序开始时,最长递增序列长度为1(每个元素都是一个长度为1的递增序列),当处理第2个元素时发现7比最长递增序列6的最大元素还要大,所以将6,7结合生成长度为2的递增序列,说明已经发现了长度为2的递增序列,依次处理,到第5个元素(10),这一过程中maxV数组的变化过程是:

6
6,7
6,7,8
6,7,8,9
6,7,8,9,10

开始处理第6个元素是1,查找比1大的最小元素,发现是长度为1的子序列的最大元素6,说明1是最大元素更小的长度为1的递增序列,用1替换6,形成新数组1,7,8,9,10。然后查找比第7个元素(2)大的最小元素,发现7,说明存在长度为2的序列,其末元素2,比7更小,用2替换7,依次执行,直到所有元素处理完毕,生成新的数组1,2,3,4,5,最后将6加入maxV数组,形成长度为6的最长递增子序列。

这一过程中,maxV数组的变化过程是:

1,7,8,9,10
1,2,8,9,10
1,2,3,9,10
1,2,3,4,10
1,2,3,4,5
1,2,3,4,5,6

当处理第10个元素(5)时,解法二需要查看9个元素(6,7,8,9,10,1,2,3,4),而解法三只需要用二分查找数组maxV中的两个元素(3, 4)。总的时间复杂度为O(nlogn)

代码:

#include <iostream>
#define MAXLENGTH 1000
using namespace std;

/*二分法查找*/
int getPosition(int index[], int len, int i, int arr[])
{
    int begin = 0;
    int end = len;
    while(begin < end)
    {
        int mid = begin + (end - begin) >> 1;
        if(arr[index[mid]] < arr[i])
            begin = mid + 1;
        else if(arr[i] < arr[index[mid]])
            end = mid;
        else
            return mid;
    }
}

//arr[] 目标数组
//n 目标数组长度
//index[i]长度为i的最长递增子序列的最后一个元素在目标数组arr[]中的下标
//s[] 最长子序列
int LongestIncreasingSubsequence(int arr[], int n, int index[], int s[])
{
    index[0] = -1;//长度为0时的下标为-1
    index[1] = 0;//长度为1时的下标为0
    int path[MAXLENGTH];
    path[0] = index[0];
    int mLen = 1;
    for(int i = 1; i < n; i++)
    {
        int pos = getPosition(index, mLen, i, arr);
        path[i] = index[pos - 1];
        index[pos] = i;
        if(pos > mLen)
            mLen = pos;
    }
    s[0] = index[mLen];
    int i = 0;
    while(path[s[i]] != -1)
    {
        s[i + 1] = path[s[i]];
        i++;
    }
    return mLen;//最长子序列的长度
}

本文参考以下博文:
http://blog.csdn.net/wenlei_zhouwl/article/details/5990696
http://blog.csdn.net/joylnwang/article/details/6766317

你可能感兴趣的:(《编程之美》——求数组的最长递增子序列)