算法 - 求最长升序子序列长度

算法 - 求最长升序子序列长度

1. 案例

求一个数组中最长升序子序列的长度。如 [8,4,7,5,1,3,6,2]; 升序子序列有 [4,7],[4,5,6],[1,3,6],最长升序子序列的长度为 3。

特别说明:因为题目要求是找到最长升序子序列的长度,并没有说找到最长升序子序列。所以在实现的过程中,只要找到一个子序列。这个子序列是升序的,并且保证是最长的。就可以知道其长度

2. 思路

方法:动态规划 + 二分查找

该方法的解题关键在于,先找到第一个升序子序列(把第一个元素移动到一个空数组,将该数组作为第一个升序子序列)。剩余的元素比这个子序列的最后一个元素大,则直接移动到该子序列尾部。否则在该序列中找到比其大的最小元素,将其替换掉。替换不会影响最长升序子序列的长度,其目的是为了让子序列里面的元素降到最小,好让剩余的元素中数值较大的元素升序排列

比如数组 [8,4,7,5,1,3,6,2] 中第一个升序序列可以理解为是 [8]。后面的元素 4 比 8 小,就替换掉 8。这个升序序列就是 [4]。接着是 [4,7],找到 5 时,5 比 7 小。找到 [4,7] 中比 5 大的最大元素,替换掉成为 [4,5]。这是为了让后面的较大元素能够升序排列。如果不替换掉 7,后面的元素 6 就没法添加到这个序列中来。这里有点绕,但也是理解这个思路的关键所在。

第一步:创建新数组
定义一个新数组 newArr=[],先将数组 arr = [8,4,7,5,1,3,6,2] 中的第一个元素移动到新数组,即 newArr=[8],arr = [4,7,5,1,3,6,2] 。

第二步:按照二分查找对比元素。
循环数组 arr 。通过二分查找法找到当前元素 arr[0] (因为每操作一次都会删除掉一个元素,所以当前元素永远是第一个元素)在新数组 newArr 中的位置。

  1. 当前元素与新数组的中间元素比较,当前元素小。左移,还小,继续左移。直到当前元素比中间元素 m 大,则替换掉 m+1 索引位置的元素。如果 m 是 0,则直接替换掉第一个元素。

    如:当前元素为 4,在数组 [3,5,6,7,9] 中找到中间元素 6。当前元素4比6小,左移,将范围定位到 [3,5,6] 。继续找到中间元素 5。当前元素还小,继续左移。将范围定位到 [3,5]。中间元素为 5。当前元素还小,将范围定位到 [3]。当前元素4比3大。替换 m+1 索引位置的元素,也就是用 4 替换 5。结果为 [3,4,6,7,9]。

  2. 当前元素与新数组的中间元素比较,当前元素大,右移。还大,继续右移。直到当前元素比中间元素 m 小,则替换掉 m 索引位置的元素。如果没有找到比当前元素还大的数。则将当前元素直接添加到新数组尾部。

    如:当前元素为 8,在数组 [3,5,6,7,9] 中找到中间元素 6 ,当前元素8比6大,右移,将范围定位到 [6,7,9],继续找到中间元素 7。当前元素还大小,继续右移。将范围定位到 [7,9]。中间元素为 9。当前元素8比9小。替换 m 索引位置的元素,也就是用 9 替换 8。结果为 [3,4,6,7,8]。

3. 代码

  var arr = [8,4,7,5,1,3,6,2];
 function lengthOfLIS(arr) {
     //判断是否为数组类型
     if(Array.isArray(arr)){
         //如果数组为空直接返回0
         if(arr.length === 0){
             return 0;
         }
         var newArr = [];
         //先将第一个元素移动到新数组中
         newArr.push(arr.shift());
         while (arr.length){
             //如果当前元素大于新数组的最后一个元素,则直接将当前元素添加进来
             if(arr[0]>newArr[newArr.length-1]){
                 newArr.push(arr.shift());
             }else {
                 //否则用二分查找法找到新数组中比当前元素大的最小元素,将其替换掉
                 var i = 0,j = newArr.length;
                 while (i<j){
                     var m = Math.floor((i+j)/2);
                     if(arr[0]>newArr[m])i=m+1;//当前元素大于中间元素,往右移
                     else j = m;//当前元素小于中间元素,往左移
                 }
                 newArr[i] = arr.shift();//找到新数组元素中比当前元素大的最小元素,用当前元素替换掉该元素。
             }
         }
         return newArr.length;
     }else {
         throw new Error("the type of param must be Array")
     }
 }
  console.log(lengthOfLIS(arr)) //3

4. 说明

最后再解释说明一下替换元素的原因。替换元素的目的其实就是给子序列降级。
比如数组 [4,7,5,1,3,8,6,2]。一眼看上去最长升序子序列是[4,5,8]。不做降级处理的话,假设后面又有一个元素 7。即 [4,7,5,1,3,8,6,2,7] 你的写法很有可能会过滤掉这个 7。而做了降低处理,上面的方法得到的子序列是 [1,2,6]。最后一个元素 7 可以如愿添加进来。成为[1,2,6,7]。长度为 4。正常的升序子序列为 [4,5,6,7],长度也为 4。再强调最后一遍,该题求的是最长子序列长度,子序列长度,子序列长度。不是子序列,不是子序列,不是子序列。当然也可以算出子序列,但是时间复杂度可能会大于 O(n*logn)。

发布了252 篇原创文章 · 获赞 2360 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/weixin_44135121/article/details/103705775