首页 > 技术文章 > 最长递增子序列

debugxw 2019-08-11 16:35 原文

题目一:最长递增子序列

题目链接:最长递增子序列

给出长度为 N 的数组,找出这个数组的最长递增子序列。递增子序列是指,子序列的元素是递增的。

例如:5 1 6 8 2 4 5 10,最长递增子序列是 1 2 4 5 10

AC 代码

演示样例

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++)
            array[i] = scanner.nextInt();

        int res = getLIS(array, 0, n);
        System.out.println(res);
    }

    // 求 array 的最长递增子序列的长度
    public static int getLIS(int[] array, int start, int length) {
        int[] ans = new int[length + 1];
        ans[1] = array[start];
        int maxLen = 1;
        for (int i = start + 1; i < start + length; i++) {
            if (array[i] > ans[maxLen]) {
                ans[++maxLen] = array[i];
            } else {
                int pos = binarySearch(ans, 1, maxLen, array[i]);
                ans[pos] = array[i];
            }
        }
        return maxLen;
    }

    // 二分找到 ans 中第一个大于等于 value 的那个位置
    private static int binarySearch(int[] ans, int left, int right, int value) {
        int mid;
        while (left < right) {
            mid = (left + right) >> 1;
            if (ans[mid] >= value)
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
}

题目二:最长非递减子序列

题目链接

小明有一袋子长方形的积木,如果一个积木 A 的长和宽都不大于另外一个积木 B 的长和宽,则积木 A 可以搭在积木 B 的上面。好奇的小明特别想知道这一袋子积木最多可以搭多少层,你能帮他想想办法吗?

定义每一个长方形的长 L 和宽 W 都为正整数,并且 1 <= W <= L <= INT_MAX,袋子里面长方形的个数为 N,并且 1 <= N <= 1000000。
假如袋子里共有 5 个积木分别为 (2, 2),(2, 4),(3, 3),(2, 5),(4, 5),则不难判断这些积木最多可以搭成 4 层,因为 (2, 2) < (2, 4) < (2, 5) < (4, 5)。

输入描述

第一行为积木的总个数 N
之后一共有 N 行,分别对应于每一个积木的宽 W 和长 L

输出描述

输出总共可以搭的层数

样例输入

5
2 2
2 4
3 3
2 5
4 5

样例输出

4

AC 代码

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

/**
 * 思路:
 *      先对长方形的宽进行一次排序,然后就变成了求长方形的长的最长非递减子序列
 */
public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[][] array = new int[n][2];
        for (int i = 0; i < n; i++) {
            array[i][0] = scanner.nextInt();
            array[i][1] = scanner.nextInt();
        }

        Arrays.sort(array, Comparator.comparingInt(o -> o[0]));
        int res = getLIS(array, 0, n);
        System.out.println(res);
    }

    // 求 array 的最长非递减子序列的长度
    public static int getLIS(int[] array, int start, int length) {
        int[] ans = new int[length];
        ans[0] = array[start];
        int maxLen = 1;
        for (int i = start + 1; i < start + length; i++) {
            int pos = binarySearch(ans, 0, maxLen, array[i]);
            maxLen = pos == maxLen ? maxLen + 1 : maxLen;
            ans[pos] = array[i];
        }
        return maxLen;
    }

    /**
     * 二分找到 ans 中第一个大于 value 的那个位置
     *
     * 这里 ans 中的数据范围为 [left, right)
     */
    private static int binarySearch(int[] ans, int left, int right, int value) {
        int mid;
        while (left < right) {
            mid = (left + right) >> 1;
            if (ans[mid] > value)
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
}

题目三:最长公共子序列

题目链接:最长公共子序列

给出 1 - n 的两个排列 P1 和 P2,求它们的最长公共子序列。

输入描述

第一行是一个数 n
接下来两行,每行为 n 个数,为自然数 1 - n 的一个排列。

输出描述

一个数,代表最长公共子序列的长度

样例输入

5
3 2 1 4 5
1 2 3 4 5

样例输出

3

AC 代码

import java.util.Scanner;

/**
 * 求两个序列的最长公共子序列是典型的动态规划题目,时间复杂度为 O(n^2)
 *
 * 但是这道题中的两个序列满足特定的条件:A B 分别是 1 - n 的两个不同的排列
 *
 * 这时可以将其转化为求一个序列的最长递增子序列,而求一个序列的最长递增子序列的时间
 * 复杂度可以优化为 O(nlogn)
 *
 * 具体实现为:
 *          因为序列 A B 是 1 - n 的两个不同的排列,那么 A 中出现的数字在 B 中
 *          也一定存在。
 *          我们可以用一个数组 pos 记录下 A 中的某个数字在序列 B 中出现的位置,那么
 *          只要求得数组 pos 的最长递增子序列的长度,也就求得了 A 和 B 的最长公共子
 *          序列的长度。
 *
 *      例如:
 *          A[]  1  5  4  2  3
 *
 *       posA[]  1  2  3  4  5
 *
 *        pos[]  5  1  2  4  3        最长递增子序列的长度为 3
 *
 *          B[]  5  4  3  2  1
 *
 *      为什么会这样呢?
 *      实际上我们找的并不是一个具体的数字序列,而是逻辑上的数字出现的顺序序列
 *
 *      如上所示,posA 代表 A 中的数字在 A 中出现的顺序,pos 代表 A 中的数字在 B 中
 *      出现的顺序。
 *      显然,只要找到 pos 中的一个递增子序列,那么这段递增子序列所对应 B 中的一个子序
 *      列一定存在与 A 中,即找到了 A 与 B 的公共子序列。
 *
 *      那么只要找到了 pos 中的最长递增子序列的长度,也就找到了 A B 的最长公共子序列的长度
 *
 */
public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] A = new int[n];
        for (int i = 0; i < n; i++)
            A[i] = scanner.nextInt();

        int[] map = new int[n + 1];
        for (int i = 0; i < n; i++) {
            int tem = scanner.nextInt();
            map[tem] = i + 1;
        }

        int[] pos = new int[n];
        for (int i = 0; i < n; i++)
            pos[i] = map[A[i]];

        int res = getLIS(pos, 0, n);
        System.out.println(res);
    }

    // 求 array 的最长递增子序列的长度
    public static int getLIS(int[] array, int start, int length) {
        int[] ans = new int[length + 1];
        ans[1] = array[start];
        int maxLen = 1;
        for (int i = start + 1; i < start + length; i++) {
            if (array[i] > ans[maxLen]) {
                ans[++maxLen] = array[i];
            } else {
                int pos = binarySearch(ans, 1, maxLen, array[i]);
                ans[pos] = array[i];
            }
        }
        return maxLen;
    }

    // 二分找到 ans 中第一个大于等于 array[index] 的那个位置
    private static int binarySearch(int[] ans, int left, int right, int value) {
        int mid;
        while (left < right) {
            mid = (left + right) >> 1;
            if (ans[mid] >= value)
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
}

题目四:Array

题目连接:Array

小红有两个长度为 n 的排列 A 和 B。每个排列由 [1, n] 数组成,且里面的数字都是不同的。

现在要找到一个新的序列 C,要求这个新序列中任意两个位置 (i, j) 满足:

如果在 A 数组中 C[i] 这个数在 C[j] 的后面,那么在 B 数组中需要 C[i] 这个数在 C[j] 的前面。

请问 C 序列的长度最长为多少呢?

输入描述

第一行一个整数,表示 N。
第二行 N 个整数,表示 A 序列。
第三行 N 个整数,表示 B 序列。
满足: N <= 50000

输出描述

输出最大的长度

样例输入

5
1 2 4 3 5
5 2 3 4 1

样例输出

4

说明

最长的 C 为 [1, 3, 4, 5]

AC 代码

import java.util.Scanner;

/**
 * 解题思路:
 *          可以先将数组 B 倒叙为 C,这样题目就转换为了求 A 和 C 的最长公共子序列的长度
 *          但如果用动态规划的话时间复杂度为 O(n^2),题目中的 n <= 50000
 *          显然会超时。
 *
 *          那么就可以直接使用题目二所用的方法,求得 A C 的最长公共子序列的长度
 *          但是这里并没有直接将 B 倒叙,而是在对 pos 赋值的时候对 map 取负,这样就达
 *          到了同样的效果。
 *
 */
public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] A = new int[n];
        for (int i = 0; i < n; i++)
            A[i] = scanner.nextInt();

        int[] map = new int[n + 1];
        for (int i = 0; i < n; i++) {
            int tem = scanner.nextInt();
            map[tem] = i + 1;
        }

        int[] pos = new int[n];
        // 对 map 取负值,就行当与求数组 pos 的最长上升子序列
        for (int i = 0; i < n; ++i)
            pos[i] = -map[A[i]];

        // 接下来相当于是找 pos 的最长上升子序列,时间复杂度 O(nlogn)
        int res = getLIS(pos, 0, n);
        System.out.println(res);
    }

    // 求 array 的最长递增子序列的长度
    public static int getLIS(int[] array, int start, int length) {
        int[] ans = new int[length + 1];
        ans[1] = array[start];
        int maxLen = 1;
        for (int i = start + 1; i < start + length; i++) {
            if (array[i] > ans[maxLen]) {
                ans[++maxLen] = array[i];
            } else {
                int pos = binarySearch(ans, 1, maxLen, array[i]);
                ans[pos] = array[i];
            }
        }
        return maxLen;
    }

    // 二分找到 ans 中第一个大于等于 array[index] 的那个位置
    private static int binarySearch(int[] ans, int left, int right, int value) {
        int mid;
        while (left < right) {
            mid = (left + right) >> 1;
            if (ans[mid] >= value)
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
}

推荐阅读