最长子序列

83 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

背景

vue diff算法中,获取到一个数组 ,数据存储的形式为新节点相对下标对应的是老节点下标+1,然后无对应老节点时,默认为-1,也就是说,如果这些可以复用的节点的顺序如果是递增的,那么说明更新后dom节点的位置是不需要移动的,但是目前怎么获取递增节点的序号呢?需要一个算法来处理

问题

当前情况如下,有老节点list1和新节点list2,当尝试前面复用后,key为a和b的处理完成, key为h和f的处理完成,剩下了e、c、d、h的节点。然后得到长度为4的数组[-1.-1.-1.-1],通过计算发现了前三个节点可以找到老节点复用,老节点的索引+1去匹配新节点的位置,得到[5,3,4,-1] image.png

目的

计算出数组[5,3,4,-1]中的最长的子序列的下标[1,2],因为子序列是3和4,然后对应的下标就是3,4. 接下来实现一下getSequence函数,首先我们假设或者说认定这个数组第一个元素一定是有序队列,这一点是毋庸置疑的,接着,获取数组的长度,然后便利这个数组,如果遍历到的元素正好小于队列中最后一个元素,那么这个元素就可以直接插入到子队列中。

function getSequence (arr) {
  let lis = [0]
  let len = arr.length
  for (let i = 0; i < len; i++) {
    let arrI = arr[i]
    let last = lis[lis.length - 1]
    if (arr[last] < arrI) {
      lis.push(i)
      continue
    }
  }
}

为了使子序列最长,总是尝试把小的放到前面,就是排序时尽量使队列缓慢递增。来尽可能的让队列长

 // 二分插入
      let left = 0,
        right = lis.length - 1
      while (left < right) {
        const mid = (left + right) >> 1
        if (arr[lis[mid]] < arrI) {
          // 在右侧
          left = mid + 1
        } else {
          right = mid
        }
      }
      // 找到了
      if (arrI < arr[lis[left]]) {
        if (left > 0) {
          // record[i] = lis[left - 1]
        }
        lis[left] = i
      }

然后得到结果可能不对,因为总是尝试将小索引的插入到队列中,有时可能前面已经形成了以比较长的子队列,后面突然出现一个比较小的数,不能因为一棵树放弃了整个森林 例如数组 [10, 3, 4, -1, 2] ,最长队列是3和4也就是下标[1,2]

通过记录每个索引的前一个索引修复索引

创建record 数组,在每次变更lis时记录好当前值的前一位的值,然后最后从最后一位往前恢复,一定是从小到大的索引位置了



function getSequence (arr) {
  let lis = [0]
  let len = arr.length
  const record = new Array(len).fill(null)
  for (let i = 0; i < len; i++) {
    const arrI = arr[i]
    if (arrI !== -1) {
      const last = lis[lis.length - 1]
      if (arr[last] < arrI) {
        lis.push(i)
        record[i] = last
        continue
      }
      // 二分插入
      let left = 0,
        right = lis.length - 1
      while (left < right) {
        const mid = (left + right) >> 1
        if (arr[lis[mid]] < arrI) {
          // 在右侧
          left = mid + 1
        } else {
          right = mid
        }
      }
      // 找到了
      if (arrI < arr[lis[left]]) {
        if (left > 0) {
          record[i] = lis[left - 1]
        }
        lis[left] = i
      }
    }
  }
  let i = lis.length
  // 获取序列中最后的索引
  let last = lis[i]
  if (i-- > 0) {
    // last 等于比最后一个数小的索引 record i 中保存的索引正好是比当前last小的索引
    last = record[i]
    lis[i] = last
  }
  return lis
}


const list = getSequence([10, 3, 4, -1, 2])
console.log(list)