最长递增子序列_贪心算法(❗❗❗经典面试题❗❗❗)

325 阅读4分钟

此算法是此题的优化版本,对比之前的动态规划中的嵌套循环的O(n2)O(n^2)来说,这个解法是O(nlog2n)O(nlog_2n).

题目链接:leetcode.cn/problems/lo…

题目

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

 输入:nums = [10,9,2,5,3,7,101,18]
 输出:4
 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

 输入:nums = [0,1,0,3,2,3]
 输出:4

示例 3:

 输入:nums = [7,7,7,7,7,7,7]
 输出:1

提示:

  • 1 <= nums.length <= 2500
  • 104<=nums[i]<=104 -10^4 <= nums[i] <= 10^4

解析

在看此解析之前建议掌握动态规划解法二分法

此方法很难想到(当然我也是在大佬那学的),但是本身的难度是不大的,只要掌握了思想就行。

我们创建一个数组ret,用于存最长子序列,ret[i]就是这个序列的最后一个元素。那么这个序列是如何来的呢?

image.png 首先遍历这个数组,当 i = 0 时 递增子序列中只有一个7,此时的长度为1

image.pngi = 1时,arr[1] = 3,此时3比7小,我们就要将序列中的7给换掉,因为能大于7的数肯定会大于3,能存在7后面的数字肯定能存在3后面。这也是这个贪心算法的核心。

image.pngi = 2时,arr[2] = 8,此时8比3大要排在后面,长度也变成了2。

image.pngi = 3时,arr[3] = 4,此时的4也是一样的把序列中的8给替换掉。

image.pngi = 4时,arr[4] = 5,此时的5直接排在后面没有问题。

image.pngi = 5时,arr[5] =2,那么此时的2我们不会直接丢掉,我们从序列中找到比2大的第一个数,也就是3,把3替换成2。

这时候就有同学问了3换成2不会对序列有影响吗,答案是此时的替换会影响到递增序列的具体值,但是不会影响到序列的长度。

因为3之后的数字都是比3大的那么比3大的数字肯定比2大,所以这个序列还是递增的。但是不是严格的按照原数组进行递增的。就像这个2跑到了4的前面一样。

最后两部直接放图

image.png

image.png 所以这个贪心算法的步骤就是:

1.如果遇到比递增序列(ret数组)中最后一个值(最大的值)大的时候,我们就放在后面。

2.这个数组中存储的是所有长度为x的递增序列中,最后一个元素最小的值(例如下标 i 为3,序列长度为2的时候,我们存的是4,而不是8),

3.当遇到arr[i]比序列中的最后一个值小的时候,我们会找序列中比这个值大的第一个数,将它替换掉(例如 i 为5的时候)。

那么这个贪心算法完成后的效果就是,假设后面遇到了1、2、3、4、5、6、7、8这样的序列,我们还是能保证他是最长的,我们不管内容如何,只管长度是对的。

那么我们下面就是怎么在这个ret数组中到这个比arr[i]大的第一个数了,暴力的话肯定就是直接for循环一遍遍历,但是我们贪心算法就是为了追求效率,而且你用了for循环之后的时间复杂度还是O(n2),和动态规划一样了。

我们可以使用二分法。这就是上面要求大家先掌握二分的原因,二分使用之后的效率就变为了O(nlog2n)O(nlog_2n)

代码

class Solution {
   public int lengthOfLIS(int[] nums) {
        //创建一个最长递增子序列表
        ArrayList<Integer> ret = new ArrayList<>();
        int n = nums.length;
        //先将第一个数丢进ret中,方便遍历的时候进行比较
        ret.add(nums[0]);
        for (int i = 1; i < n; i++) {
            //第一种情况,如果这个数大于ret数组中的最大值,那么直接插入到最后就行
            if (nums[i] > ret.get(ret.size() - 1)) {
                ret.add(nums[i]);
            } else {//进行二分查找,寻找插入位置
                int left = 0;
                int right = ret.size() - 1;
                while (left < right) {
                    int mid = (left + right) / 2;
                    if (ret.get(mid) < nums[i]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                //直接插入
                ret.set(left,nums[i]);
            }
        }
       	//此时ret的长度就是最长递增序列的最大值
        return ret.size();
    }
}