Java 算法-最长上升序列(动态规划或二分法)

  今天在lintCode上面做了一道关于二分法的题,觉得有必要记录下来。

1.概览

(1).题意

给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。

(2).说明

  最长上升子序列的定义:
  最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。

(3).样例

给出 [5,4,1,2,3],LIS 是 [1,2,3],返回 3
给出 [4,2,4,5,3,7],LIS 是 [2,4,5,7],返回 4

(4).挑战

  要求时间复杂度为O(n^2) 或者 O(nlogn)

2.解题思路

  最长上升子序列的经典解法是动态规划,也是最简单的解法,但是动态规划的时间复杂度是O(n^2),说实话,还是有点高。本文也不会讲解动态规划的解法,但是会贴出代码,本文的重点在于讲解二分法如何解决这个问题。
  二分法的解决问题的关键在于,开辟一个数组,用来存储当前形成长度为k序列的最后一个元素。例如,我们假设数组为a[],a[k]存储的就是最长序列为k+1,在nums数组中的最后一个元素。可能有人还是看不太懂,这里举一个实际的例子。
  假设数组nums[] = {5,4,1,2,3},数组a[]用来存储当前形成长度为k序列的最后一个元素。则a[0] = 1, a[1] = 2, a[2] = 3。其实我们在遍历数组nums时,最开始a[0] = 5,然后a[0] = 4, 最后a[0] = 1。
  通过这个特性,我们知道a数组里面的存储的数字是单调递增的,那么当我们遍历nums数组的第i个数字,如果a[len - 1] < nums[i]的话,那么a[++len] = nums[i](len表示当前最长的序列的长度);反之,我们使用二分法查找法查找a数组,找到从左往右第一个大于等于nums[i]的位置,将这个位置上面的元素更新为nums[i]就行了,这样能保证整个a数组是单调递增的。
  最终len就是最长上升序列的长度。

3.代码

  理解了上面的思路,下面的二分法代码就非常容易理解。这里先贴出动态规划的代码。

(1).动态规划

    public int longestIncreasingSubsequence(int[] nums) {
        int a[] = new int[nums.length];
        a[0] = 1;
        int max = Integer.MIN_VALUE;
        for(int i = 1; i < nums.length; i++) {
            int num = Integer.MIN_VALUE;
            for(int j = i - 1; j >= 0; j--) {
                if(nums[j] < nums[i]) {
                    num = Math.max(num, a[j] + 1);
                }else {
                    num = Math.max(num, 1);
                }
            }
            a[i] = num;
            max = Math.max(num, max);
        }
        
        return max;
    }

(2).二分法

    public int longestIncreasingSubsequence(int[] nums) {
        if(nums.length == 0){
            return 0;
        }
        int a[] = new int[nums.length];
        //初始化a[0]
        a[0] = nums[0];
        int len = 0;
        for(int i = 1; i < nums.length; i++) {
            //如果当前nums的数字大于a[len](a数组有值的最后一个元素)
            //那么a[++len] = nums[i]
            if(nums[i] > a[len]) {
                a[++len] = nums[i];
            }else {
                //反之使用二分查找法,查找从左往右第一个大于等于nums[i]的位置
                //然后将该位置更新为
                int index = binarySearch(a, len, nums[i]);
                a[index] = nums[i];
            }
        }
        //记得这里是len + 1,因为len是从0开始的
        return len + 1;
    }
    private int binarySearch(int a[], int len, int ans) {
        int low = 0;
        int height = len;
        while(low <= height) {
            int mid = (height + low) / 2;
            if(a[mid] >= ans) {
                height = mid - 1;
            }else {
                low = mid + 1;
            }
        }
        return low;
    }
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 14,089评论 0赞 89
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 13,127评论 0赞 33
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,751评论 0赞 18
  • 描述 给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。 说明 最长上升子序列的定义:最长上升子序...
    6默默Welsh阅读 783评论 1赞 0
  • [cp]#长沙约拍##长沙约片# 我想给你我能企及的所有美好。——致我终年不遇的你!
    摄影师心一阅读 196评论 0赞 0

友情链接更多精彩内容