寻找数组最大递增子序列

本文介绍了寻找最大递增子序列的算法,重点分析了一种优秀实现方式,该方法通过遍历数组并保存比当前项小的最近项索引来找到最大递增子序列的索引。在遍历过程中,使用中位数比较策略优化了搜索效率,减少了遍历次数。同时,文章提供了不同实现的代码示例,包括基于索引的版本和包含项及索引的版本,并对比了其他常见算法。
摘要由CSDN通过智能技术生成

寻找最大递增子序列是比较常见的算法,实现方式也不尽相同,此文稍作介绍。

我是算法世界的小学生,因为最近再看 vue3 Diff 有一个寻找最大递增子序列的使用,遂为了弄明白花了些时间并去网上网罗了一些实现方法,但重点分析 这种方法 的实现思路,我认为是比较优秀的方式。


一种好的实现方式分析

这种方式找的是最大递增子序列的索引,而不是项。主要说一下思路:

  1. 遍历数组
  2. arr 拷贝一份(p),遍历过程中用来保存比当前项小的最近项的索引。
  3. result 保存最后的返回值,遍历过程中遇到比 result 最后一项大的 就 push 相应的索引,否者更新result中对应项比当前项大且不相等的一项。注意是索引。
  4. 用一个变量保存在 result 中大于当前项的索引(u,也是找中位数的起始项索引),一个变量保存中位数索引( c )
    一个变量保存取中位数的结束项索引(v)。
  5. 利用 p 修正数组

注意点:

  • p 每次更新都是从当前 result 中找出索引对应的数组项与当前项相比,如果存在比当前项小的项,那么 p 相应的项更新为找出项的在数组的索引。否者不更新。
  • result 仅在遇到相等项时不更新,否者就更新。

文字略显干涩且绕口难以理解,来点图例解析

例子

  • 为了便于理解,result和p里面保存的是项,而不是索引。则result 初始值为 [arr[0]]
  • 浅蓝代表遍历的当前项
  • i 代表遍历的索引值

[2, 7, 10, 6, 3, 1, 10, 1, 1] 的最大递增子列。

i=0,在这里插入图片描述

i=1,
在这里插入图片描述

i=2,
在这里插入图片描述

i=3, 当前项 current = 6, 6 始终和 result[c] 作比较

  1. u=0,v=2 (result.length-1); c=1; 6<7
  2. u=0,v=1,c=0,6>2
  3. u=1,v=1,循环结束,于是 result[u] = 6, p[3] = 2

总结,result中比当前项大的更新为当前项,result中比当前项小的项用来更新p相应的项。
在这里插入图片描述

i=4,

  • 3>result中的2, 将 2 之后的一项更新为 3 。并且将 result 中的 2 更新 p 相应的项

在这里插入图片描述

i=5,

  • 当前项小于 result 中的最小项,p 不做更新。result 第一项 2 更新为 1

在这里插入图片描述

i=6,

  1. u=0, v=2; 10>result[1]= 3;
  2. u=2v=2; 10不大于result[2] = 10;且不小于10,那么 p不更新(u虽然大于0,但是10不小于result[2])。result也不更新

在这里插入图片描述

i=7,

  • u最后=0。1不大于result中最小项,则p不更新,又因为1不大于result最大项,result也不更新。

在这里插入图片描述

i=8, 处理同 i=7 ,均不更新,这里我添加了result的对应关系
在这里插入图片描述

最后由 presult 进行调整,从后向前遍历 result
先找 10,设10在 Arr 索引为index ,对应 p 里前一个元素(p[index])时 7 ,将 result10 的前一个元素3 改为 7,在这里插入图片描述

再看 7,对应 p 中保存的前一个元素 p[1] 2,则将 result 第一个元素改为 2.
在这里插入图片描述

找最大递增子序列索引【完整代码】

/*
[3,1,5,4,2] => [1,2]
*/
function lis(arr) {
  const p = arr.slice();
  const result = [0]; // 索引数组
  let i;
  let j;
  let u;
  let v;
  let c;
  const len = arr.length;
  for (i = 0; i < len; i++) {
    const arrI = arr[i];
    if (arrI !== 0) {
      // 取最后一个元素
      j = result[result.length - 1];
      if (arr[j] < arrI) {
        p[i] = j;
        result.push(i);
        continue;
      }
      u = 0;
      v = result.length - 1;
      //result长度大于1时
      while (u < v) {
        // 取中位数
        c = ((u + v) / 2) | 0;
        if (arr[result[c]] < arrI) {
          u = c + 1;
        } else {
          v = c; //result中位数大于等于 当前项。v取中位数
        }
      }
      if (arrI < arr[result[u]]) {
        if (u > 0) {
          p[i] = result[u - 1];
        }
        result[u] = i;
      }
    }
  }
  u = result.length;
  v = result[u - 1];
  while (u-- > 0) {
    result[u] = v;
    v = p[v];
  }
  return result;
}

找最大递增子序列(含项和索引,这是我的小改版)【完整代码】

function lisItem(arr){
   let p = arr.slice();
   let result = [{ele: arr[0], idx: 0}];
   let j;
   let i;
   // c, u, v 是保存 result 中 的中位数、确定中位数的起始位置、确定职位数的结束位置。
   let u;
   let c;
   let v;
   const len = arr.length;
   for(i =0; i< len; i++){
     const current = arr[i];
     if(current !== 0) {
       j = result.length - 1;
       if(result[j].ele < current) {
         console.log(' > : ', i, current);
         result.push({ele: current, idx: i})
         p[i] = result[j].idx;
       }else {
         u = 0;
         v = result.length - 1;
         while(u < v) {
           c = (u + v) >> 1; // (prev+last)/2 | 0;
           if(current > result[c].ele){
             u = c + 1;
           }else {
             v = c;
           }
         }
         debugger;
         // 上面的while主要找出 大于等于当前项的索引值 u
         console.log('修改: ',JSON.stringify(result), u, i, current);
         if(current < result[u].ele) { // 不断的修正result
           if(u>0) {
             const indexOfArr =  arr.indexOf(result[u-1].ele) // 大于等于当前项的前一项的索引
             indexOfArr !== -1 && (p[i] = indexOfArr)
           }
           result[u] = {ele: current, idx: i}
         }
       }
     }
   }
   u = result.length;
   v = result[result.length - 1];
   while(--u > 0){
     result[u] = { ele: arr[v.idx], idx: v.idx };
     v = { ele: arr[p[v.idx]], idx: p[v.idx] }
   }
   return result;
 }

这篇文章也说的比较清晰,推荐一看 Vue3 DOM Diff 核心算法解析


其它的实现方法

来自 vue-design

const seq = [0, 8, 4, 12, 2, 10]

function lis(seq) {
  const valueToMax = {}
  let len = seq.length
  for (let i = 0; i < len; i++) {
    valueToMax[seq[i]] = 1
  }

  let i = len - 1
  let last = seq[i]
  let prev = seq[i - 1]
  while (typeof prev !== 'undefined') {
    let j = i
    while (j < len) {
      last = seq[j]
      if (prev < last) {
        const currentMax = valueToMax[last] + 1
        valueToMax[prev] =
          valueToMax[prev] !== 1
            ? valueToMax[prev] > currentMax
              ? valueToMax[prev]
              : currentMax
            : currentMax
      }
      j++
    }
    i--
    last = seq[i]
    prev = seq[i - 1]
  }

  const lis = []
  i = 1
  while (--len >= 0) {
    const n = seq[len]
    if (valueToMax[n] === i) {
      i++
      lis.unshift(len)
    }
  }

  return lis
}

最长连续递增子序列, 来自使用JS找出数组中的最长递增子串

 function findL(arr){
    let arr_act=[];
    let arr_pre=[];
    let arr_max=[];

    for(var i=0;i<arr.length;i++){
      let val=arr[i]
      if(arr_act.length==0){
        arr_act.push(val)
      }else if(val<arr_act[arr_act.length-1]){
        arr_pre=arr_act;
        arr_act=[];
        arr_act.push(val)
      }else {
        arr_act.push(val)
      }

      if(arr_pre.length>arr_max.length){
        arr_max=arr_pre;
      }

    }
    return arr_max.length>arr_act.length?arr_max:arr_act
  }

最长递增子序列, 来自使用JS找出数组中的最长递增子串

function lis(arr){
  let arr_max=[];
  let cache=[];
  for(var i=0;i<arr.length;i++){
    cache.push([])
  }
  for(let i=0;i<arr.length;i++){
    for(let j=0;j<i;j++){
      if(arr[j]<arr[i]){
        if(cache[i].length<cache[j].length+1){
          cache[i]=[].concat(cache[j])
        }
        
      }
    }
    cache[i].push(arr[i]);
    if(cache[i].length>arr_max.length){
      arr_max=[].concat(cache[i])
    }
  }
  return arr_max
}


总结

基本核心要点就是比对当前项与上一项的大小,保存响应的状态最后做更新。
我说的那种方法有什么亮点呢,主要是这里

while (u < v) {
  // 取中位数
  c = ((u + v) / 2) | 0;
  if (arr[result[c]] < arrI) {
    u = c + 1;
  } else {
    v = c; //result中位数大于等于 当前项。v取中位数
  }
}
if (arrI < arr[result[u]]) {
  if (u > 0) {
    p[i] = result[u - 1];
  }
  result[u] = i;
}
  • 与中位比大小能省去遍历项,数据量越大效果越明显。
  • 用的变量非常少,思路非常凝练。值得学习的方式。

参考

【个人博客】vue-design
【CSDN】使用JS找出数组中的最长递增子串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值