最长递增子序列LIS

184 阅读1分钟

一、题目

image.png

- 思路1:动态规划

  1. dp[i]定义为第i个元素结尾的最长递增子序列
  2. 在[0,i)中找到j,满足nums[j]<nums[i],那么dp[i]=Math.max(dp[i],dp[j]+1)
  3. 遍历dp[]数组,返回其中的最大值
public int lengthOfLIS(int[] nums) {
       int[] dp = new int[nums.length];
       // 初始化:每个元素都是长度为1的子序列
       for(int i=0;i<nums.length;i++){
           dp[i]=1;
       }
       for(int i=0;i<nums.length;i++){
           // 寻找dp[j]
           for(int j=i;j>=0;j--){
               if(nums[j]<nums[i]){
                   // 更新dp[i]
                   dp[i]=Math.max(dp[i],dp[j]+1);
               }
           }
       }
       int ans = Integer.MIN_VALUE;
       for(int i=0;i<dp.length;i++){
           ans = Math.max(ans,dp[i]);
       }
       return ans;
    }
  • 时间复杂度:O(n²),两次循环遍历
  • 空间复杂度:O(n)

- 思路2:贪心算法+二分法

dp[i]:长度为i的子序列结尾字符的最小值。因为结尾字符越小,就越有可能跟后面的字符拼接为一个更长的子序列。而每次遇到一个新的元素,都尝试去更新dp[]数组,使得其中的元素更小。(贪心算法:每次取局部最优)

由dp[i]的定义可知,对于dp[],满足i<j,即有dp[i]<dp[j],所以最长递增子序列就是dp[]的长度

证明:对于dp数组中任意的i<j,都有dp[i]<dp[j]

假设dp[i] (a1,a2,...ai)>=dp[j] (b1,b2,...bi,bi+1,...bj),也就是说ai>=bj,由于[bi,bj]是一个单调递增的序列,bi<bj,那么ai>=bj>bi,所以ai>bi,存在一个更小的长度为i的结尾元素,不符合dp数组的定义。因此,dp数组是一个单调递增的序列

二分法:因为dp数组是有序的,所以寻找需要更新的元素位置时,可以使用二分法快速定位

 public int lengthOfLIS(int[] nums) {
        // dp[i]:长度为i+1的子序列的结尾元素的最小值
       int[] dp = new int[nums.length];
       // 初始化
       dp[0]=nums[0];
       // 记录dp数组的长度
       int len = 0;
       // 每次遇到一个新的元素
       for(int i=1;i<nums.length;i++){
           // 如果比dp[len]大,直接插入到dp数组的末尾
           if(nums[i]>dp[len]){
               len++;
               dp[len]=nums[i];
           }
           // 使用二分法找到dp[]中第一个大于nums[i]的元素,使其变小
           else{
               int left = 0;
               int right = len;
               while(left<right){
                   int mid = (right+left)/2;
                   if(nums[i]>dp[mid]){
                       left = mid+1;
                   }else{
                       right = mid;
                   }
               }
               // 把nums[i]插入
               dp[left] = nums[i];
           }
       }
       // 需要返回长度
       len++;
       return len;
    }
  • 时间复杂度:O(nlogn),一次外层循环时间复杂度为n,内层while循环使用二分法时间复杂度为logn。
  • 空间复杂度:O(n)

而这种思路跟耐心排序非常相似,可参考文章blog.csdn.net/stronglyh/a…