最长递增子序列

来源:互联网 发布:免费qq软件下载 编辑:程序博客网 时间:2024/05/22 05:06

最长递增子序列

题目描述

Redraiment是走梅花桩的高手。Redraiment总是起点不限,从前到后,往高的桩子走,但走的步数最多,不知道为什么?你能替Redraiment研究他最多走的步数吗?
样例输入
6
2 5 1 5 4 5
样例输出
3

提示
从第1格开始走,最多为3步, 2 4 5
从第2格开始走,最多只有1步,5
从第3格开始走最多有3步,1 4 5
从第5格开始走最多有2步,4 5
所以这个结果是3。

接口说明
方法原型:
int GetResult(int num, int[] pInput, List pResult);
输入参数:
int num:整数,表示数组元素的个数(保证有效)。
int[] pInput: 数组,存放输入的数字。
输出参数:
List pResult: 保证传入一个空的List,要求把结果放入第一个位置。
返回值:
正确返回1,错误返回0

思路1:

首先将【2、5、1、5、4、5】排序,得到【1,2,4,5,5,5】,然后求排序前后的最长公共子序列
【2、5、1、5、4、5】与【1,2,4,5,5,5】的最长公共子序列是【2,4,5】或【1,4,5】,刚好是最长递增子序列。

最长公共子序列的算法详见:http://blog.chinaunix.net/uid-26548237-id-3374211.html

1、序列str1和序列str2,长度分别为m和n

创建1个二维数组L[m.n],初始化L数组内容为0
m和n分别从0开始,m++,n++循环:
  如果str1[m] == str2[n],则L[m,n] = L[m - 1, n -1] + 1;
  如果str1[m] != str2[n],则L[m,n] = max{L[m,n - 1],L[m - 1, n]}
最后从L[m,n]中的数字一定是最大的,且这个数字就是最长公共子序列的长度

2、从数组L中查找一个最长的公共子序列

i 和 j 分别从m,n开始,递减循环直到i = 0,j = 0。其中,m和n分别为两个串的长度。
如果str1[i] == str2[j],则将str[i]字符插入到子序列内,i–,j–;
如果str1[i] != str[j],则比较L[i,j-1]与L[i-1,j],L[i,j-1]大,则j–,否则i–;(如果相等,则任选一个)

这里写图片描述

不过,这种方法有一个问题,就是对于【1、1、1】这样的数组求解很麻烦。排序后仍为【1、1、1】,排序前后的最长公共子序列为【1、1、1】,但它的最长递增子序列为仅为【1】~

java 代码为:

// 对于【1、1、1】这样的数组求解有bugpublic static int GetResult_LCS(int num, int[] pInput, List pResult) {    if(pInput == null || pInput.length != num) {           return 0;    }    int[] temp = Arrays.copyOf(pInput, num); // 复制数组    Arrays.sort(pInput); // 排序    // LCS 最长公共子序列    int[][] arr = new int[num + 1][num + 1];    for(int i = 0; i < num + 1; i++) {        arr[i][0] = 0;    }    for(int j = 0; j < num + 1; j++) {        arr[0][j] = 0;    }    for(int i = 1; i < num + 1; i++) {                  for(int j = 1; j < num + 1; j++) {            if(pInput[i-1] == temp[j-1]) {                arr[i][j] = arr[i - 1][j - 1] + 1;            }else {                arr[i][j] = Math.max(arr[i][j-1], arr[i-1][j]);            }        }    }    pResult.add(arr[num][num]);    return 1;}

思路2:

temp是一个数组,temp[i]表示以第 i 个元素为结尾的 最长递增子序列的长度。比如:
i = 0时,以2为结尾的最长递增子序列是:【2】,长度是1
i = 1时,以5为结尾的最长递增子序列是:【2、5】,长度是2
i = 2时,以1为结尾的最长递增子序列是:【1】,长度是1
i = 3时,以5为结尾的最长递增子序列是:【2、5】或【1、5】长度是2
i = 4时,以4为结尾的最长递增子序列是:【2、4】或【1、4】长度是2
i = 5时,以5为结尾的最长递增子序列是:【2、4、5】或【1、4、5】长度是3

这里写图片描述

综上,可以使用一个公式概括上述步骤:

扫描数组,对每一元素 i,
  扫描它前面的所有元素 j,
  如果 j < i 并且 temp[j] + 1 > temp[i],
  那么 temp[i] =temp[j] + 1。

说明以 j 为结尾的最长递增子序列,在末尾加上i,就能构成一个更长的递增子序列。 比如:【2、4】加上【5】就能构成一个更长的子序列【2、4、5】

将上述思想转化为程序,Java代码如下:

import java.util.Scanner;import java.util.List;import java.util.LinkedList;public class Main {    public static int GetResult(int num, int[] pInput, List pResult) {        if(pInput == null || pInput.length != num) {            return 0;        }        int[] temp = new int[num];        int max = 1;        for(int i = 0; i < num; i++) {            temp[i] = 1;            for(int j = 0; j < i; j++) {                if(pInput[j] < pInput[i] && temp[j] + 1 >= temp[i]) {                    temp[i] = temp[j] + 1;                    max = Math.max(max, temp[i]);                }            }        }        pResult.add(max);        return 1;    }    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while(sc.hasNext()) {            int n = sc.nextInt();            int[] array = new int[n];            for(int i = 0; i < n; i++) {                array[i] = sc.nextInt();            }            LinkedList<Integer> list = new LinkedList<>();            int result = GetResult(n, array, list);            if(result == 1) {                System.out.println(list.get(0));            }else {                // TODO 错误处理                System.out.println(0);            }        }        sc.close();    }}

显然,这个方法需要两层循环,时间复杂度是O(n^2)。我们还可以对其进行优化~

思路3:O(nlogn)

用一个数组h 保存“最小最长递增子序列”。原则:初始为第一个元素h=【2】,之后的每个元素,找到h中第一次比它大于等于的元素并替换它,如果没有则加在后面。

以【2,5,1,5,4,5】为例,
i = 0,h [0] = 【2】
i = 1,h [1] = 【2、5】
i = 2,h [2] = 【1、5】
i = 3,h [3] = 【1、5】
i = 4,h [4] = 【1、4】
i = 5,h [5] = 【1、4、5】

则最长递增子序列的长度为3

参考自 http://blog.csdn.net/qq_16949707/article/details/53045417

Java代码如下:

public static int GetResult_DPNLOGN(int num, int[] pInput, List pResult) {    if(pInput == null || pInput.length != num) {         return 0;    }    LinkedList<Integer> temp = new LinkedList<>();    temp.add(pInput[0]);    for(int i = 1; i < num; i++) {        boolean flag = true;        for(int j = 0; j < temp.size(); j++) {            if(pInput[i] <= temp.get(j)) {                temp.set(j, pInput[i]);                 flag = false;                break;            }        }        if(flag) {            temp.add(pInput[i]);        }   }   pResult.add(temp.size());   return 1;}

牛客网里有一道题,就是有关最长递增子序列的,题中要求时间复杂度是O(nlogn)
https://www.nowcoder.com/practice/585d46a1447b4064b749f08c2ab9ce66?tpId=49&&tqId=29347&rp=2&ru=/activity/oj&qru=/ta/2016test/question-ranking

今天就到这里吧,拜拜~~

这里写图片描述