LeetCode 300. 最长上升子序列及二分查找方法的多种应用

给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
来源:力扣(LeetCode)

方法一:
时间复杂度为O(n2),思路很直接,采用动态规划方法记录每个数字作为结尾时的最长上升子序列长度。当遍历到nums[i]时,向前查找所有nums[j]<nums[i]。并记录nums[j]对应的最长上升子序列长度中最大值,得到nums[i]做结尾时的最长长度。因为遍历过程中向前反向遍历,时间复杂度O(n2)。

  public int lengthOfLIS(int[] nums) {
        int len=nums.length;
        if(len<1){
            return 0;
        }
         int max=0;
         int[]dp=new int[len];
         dp[0]=1;
         for(int i=0;i<len;i++){     //遍历数组
             int maxV=0;
           for(int j=i-1;j>=0;j--){   //向前遍历查找
               if(nums[j]<nums[i]){
                   maxV=Math.max(maxV,dp[j]);
               }
           }
           dp[i]=maxV+1;
           max=Math.max(max,dp[i]);
         }
         return max;
    }

方法二:
这是提示我们进行优化的方法,看到log n,想到可能是用二分查找方法。但是二分查找必须是有序数组中才能使用,那么哪里是有序的呢?这里采用辅助数组d[i]来记录长度为i的子序列的末尾的最小数值。
根据定义可以反证,当i>j时,一定有d[i]>d[j]。即这个数组时单调的,所以可以使用二分查找。
假设当前上升序列最长为end,当遍历到nums[i]时,比较d[end]和nums[i]。
如果d[end]<nums[i],那么可以直接添加到后面构成更长的序列,即d[end+1]=nums[i]。
否则就向前二分查找最大的d[j]<nums[i],那么可以令d[j+1]=nums[i]。
这样整个复杂度就降低到 O(n log n) 。

//贪心算法加二分查找
    public int lengthOfLIS2(int[] nums){
        int len=nums.length;
        if(len<1){
            return 0;
        }
        int[] d=new int[len];
        int end=0;  //记录序列当前最长长度
        d[0]=nums[0];  初始长度为1,这里以i+1表示长度
        for(int i=1;i<len;i++){
            if(nums[i]>d[end]){
                d[end+1]=nums[i];
                end++;   //如果找到大的数值,延长序列
            }else {
                int index=biSearch(d,end,nums[i]);  //二分查找
                d[index+1]=nums[i];  
            }
        }
        return end+1;  //长度是i+1
    }
    public int biSearch(int[] nums, int end, int n){
        int begin=0;
        int mid=0;
        if(n<=nums[0]){
            return -1;  //如果查找的值比最小值还小,返回-1,更新下标0处数值
        }
        while(begin<end){   
            mid=(begin+end+1)/2;
            if(nums[mid]>=n){
                end=mid-1;  
            }else if(nums[mid]<n){
                begin=mid;
            }
        }
        return end;
    }

注解:
关于二分查找的边界条件
二分查找的问题很常见,我以为自己很熟悉了。但是一直没有注意过总结边界条件问题,总是有一些特殊case下造成死循环等问题。

    /**
     *  查找固定值
     */
   public static int binarySearch(int[] nums, int n){
        int begin=0;
        int end=nums.length-1;
        int mid=0;
        if(n<nums[0]||n>nums[end]){
            return -1;
        }
        while(begin<=end){
            mid=(begin+end)/2;
            if(nums[mid]==n){
                //相等的就返回对应下标
                return mid;
            }else if(nums[mid]>n){
                end=mid-1;
            }else if(nums[mid]<n){
                begin=mid+1;
            }
        }
        return -1;
    }
    /**
     *  查找第一个小于给定值的位置
     */
    public static int binarySearch2(int[] nums, int n){
        int begin=0;
        int end=nums.length-1;
        /*首先要保证初始情况下能满足要求
        即begin和左边都是小于的,end和右边都是大于等于的
        */
        if(nums[0]>=n){
            return -1;
        }
        if(nums[end]<n){
            return end;
        }
        int mid=0;
        while(begin<end){
            mid=(begin+end+1)/2;
           if(nums[mid]>=n){
                end=mid-1;
            }else {
                begin=mid;
            }
        }
        return end;
    }
    /**
     *  查找第一个大于给定值的位置
     */
    public static int binarySearch3(int[] nums, int n){
        int begin=0;
        int end=nums.length-1;
        /*
          首先要保证初始情况下能满足要求
          即begin和左边都是小于等于的,end和右边都是大于的
        */
        if(nums[0]>n){
            return 0;
        }
        if(nums[end]<=n){
            return end+1;
        }
        int mid=0;
        while(begin<end){
            mid=(begin+end)/2;
            if(nums[mid]>n){
                end=mid;
            }else {
                begin=mid+1;
            }
        }
        return begin;
    }

如果begin存在不移动的情况,mid=(begin+end+1)/2;如果有变存在不移动情况,mid=(begin+end)/2;避免两个数字的时候原地循环。

发布了47 篇原创文章 · 获赞 1 · 访问量 1584

猜你喜欢

转载自blog.csdn.net/chinamen1/article/details/104860879