1

例子:3 1 2 7 5 8 6 9 4

v1.0.0

从前到后循坏,找到所有的可能性

const arr = [3, 1, 2, 7, 5, 8, 6, 9, 4]
for (let i = 0; i < arr.length; i++) {
    const n = arr[i]
    console.log(i, n)
    // todo
}

i = 0,可能最长组合:

3 ... // i = 0, n = 3

i = 1,可能最长组合:

3 ... // i = 0, n = 3
1 ... // i = 1, n = 1

i = 2,可能最长组合:

3 ...   // i = 0, n = 3
1 ...   // i = 1, n = 1
1 2 ... // i = 2, n = 2

由此类推:
i = 9,最终组合:

0 1 2 3 4 // 序号

3         // i = 0, n = 3
1         // i = 1, n = 1
1 2       // i = 2, n = 2
1 2 7     // i = 3, n = 7
1 2 5     // i = 4, n = 5
1 2 7 8   // i = 5, n = 8
1 2 5 8
1 2 5 6   // i = 6, n = 6
1 2 7 8 9 // i = 7, n = 9
1 2 5 8 9
1 2 5 6 9
1 2 4     // i = 8, n = 4

找到了最长的递增子序列为1 2 7 8 9,1 2 5 8 9,1 2 5 6 9

v2.0.0

我们可以发现

1 2 7 ...
1 2 5 ...

1 2 5能带来的可能,包含了1 2 7能带来的所有可能

1 2 5 8 ...
1 2 5 6 ...

1 2 5 6能带来的可能,包含了1 2 5 8能带来的所有可能

所以我们只需要用到相同序号下的最小值

以 i = 5, n = 8 为例:

0 1 2 // 序号

3 ...
1 ...
1 2 ...
1 2 7 ...
1 2 5 ...

变成

3 ...
1 ...
1 2 ...
1 2 7 ...
1 2 5 ...
1 2 5 8 ...

序号2,对应的值[7, 5],最小值是5,这里只需用到1 2 5

上面最终组合:

3         // i = 0, n = 3
1         // i = 1, n = 1
1 2       // i = 2, n = 2
1 2 7     // i = 3, n = 7
1 2 5     // i = 4, n = 5
1 2 5 8   // i = 5, n = 8
1 2 5 6   // i = 6, n = 6
1 2 5 6 9 // i = 7, n = 9
1 2 4     // i = 8, n = 4

找到了最长的递增子序列为1 2 5 6 9,数据过大时,大大降低了循环次数

注意:如果是要找到所有的可能性,只能牺牲性能,但很多时候,我们只需要找到其中最长的一种可能就行了,比如:vue的虚拟节点对比

v3.0.0

前面中,每次都需要先找到对应序号下最小的一位,然后再来对比,其实我们可以把每个序号下面的最小值用一个数组(min_arr)记录下来

0 1 2 3 4 // 序号

3         // i = 0, n = 3, min_arr = [3]
1         // i = 1, n = 1, min_arr = [1]
1 2       // i = 2, n = 2, min_arr = [1, 2]
1 2 7     // i = 3, n = 7, min_arr = [1, 2, 7]
1 2 5     // i = 4, n = 5, min_arr = [1, 2, 5]
1 2 5 8   // i = 5, n = 8, min_arr = [1, 2, 5, 8]
1 2 5 6   // i = 6, n = 6, min_arr = [1, 2, 5, 6]
1 2 5 6 9 // i = 7, n = 9, min_arr = [1, 2, 5, 6, 9]
1 2 4     // i = 8, n = 4, min_arr = [1, 2, 4, 6, 9]

由v2.0.0可知,我们只需要和每个序号下最小值来对比,也就是当前n直接和min_arr对比

思路:

  1. 如果当前n比min_arr最后一项大,则直接放到末尾,如:i = 3, n = 7,7比2大,7直接放在2的后面
  2. 如果当前n比min_arr最后一项小,从后往前循环min_arr,找到比n小的一项为止,n放在其后面,此时原后面这一项肯定比n大,所以替换原后面这一项;如:i = 8, n = 4,一直往前找,2比4小,停止循环,4放在2后面,2后面原来的5肯定是比4大的,不然到5就应该结束了,所以直接替换原来的5(代码中会采用二分法查找

v4.0.0

此时我们发现最终得到的min_arr的值虽然是不正确的,但是最后一位的值肯定是正确的,如果我们转变思维,标记最后一位在谁的后面,然后一层一层的往前标记,这个事情是不是就成了,如

0 1 2 3 4 // 序号

3         // i = 0, n = 3, min_arr = [3], 3前面为undefined
1         // i = 1, n = 1, min_arr = [1], 1前面为undefined
1 2       // i = 2, n = 2, min_arr = [1, 2], 2前面为1
1 2 7     // i = 3, n = 7, min_arr = [1, 2, 7], 7前面为2
1 2 5     // i = 4, n = 5, min_arr = [1, 2, 5], 5前面为2
1 2 5 8   // i = 5, n = 8, min_arr = [1, 2, 5, 8], 8前面为5
1 2 5 6   // i = 6, n = 6, min_arr = [1, 2, 5, 6], 6前面为5
1 2 5 6 9 // i = 7, n = 9, min_arr = [1, 2, 5, 6, 9], 9前面为6
1 2 4     // i = 8, n = 4, min_arr = [1, 2, 4, 6, 9], 4前面为2

先找到9,然后6,然后5,然后2,然后1,此时就找到了最长的递增子序列为1 2 5 6 9

最终

在程序中我们需要把值对应成索引,才能更好的找到其位置

0 1 2 3 4 5 6 7 8 // 索引
3 1 2 7 5 8 6 9 4 // 值

上面v4.0.0变成

0 1 2 3 4 // 序号

0         // i = 0, min_arr = [0], 0前面为undefined
1         // i = 1, min_arr = [1], 1前面为undefined
1 2       // i = 2, min_arr = [1, 2], 2前面为1
1 2 3     // i = 3, min_arr = [1, 2, 3], 3前面为2
1 2 4     // i = 4, min_arr = [1, 2, 4], 4前面为2
1 2 4 5   // i = 5, min_arr = [1, 2, 4, 5], 5前面为4
1 2 4 6   // i = 6, min_arr = [1, 2, 4, 6], 6前面为4
1 2 4 6 7 // i = 7, min_arr = [1, 2, 4, 6, 7], 7前面为6
1 2 8     // i = 8, min_arr = [1, 2, 8, 6, 7], 8前面为2

先找到索引7,然后6,然后4,然后2,然后1,此时索引为1 2 4 6 7,对应的最长的递增子序列值为1 2 5 6 9

源代码

function getSequence(arr:number[]) {
  const len = arr.length
  const min_arr = [0] // 存储最小的索引,以索引0为基准
  const prev_arr = arr.slice() // 储存前面的索引,slice为浅复制一个新的数组
  let last_index
  let start
  let end
  let middle
  for (let i = 0; i < len; i++) {
    let arrI = arr[i]
    // 1. 如果当前n比min_arr最后一项大
    last_index = min_arr[min_arr.length - 1]
    if (arr[last_index] < arrI) {
      min_arr.push(i)
      prev_arr[i] = last_index // 前面的索引
      continue
    }
    // 2. 如果当前n比min_arr最后一项小(二分类查找)
    start = 0
    end = min_arr.length - 1
    while(start < end) {
      middle = (start + end) >> 1 // 相当于Math.floor((start + end)/2)
      if (arr[min_arr[middle]] < arrI) {
        start = middle + 1
      } else  {
        end = middle
      }
    }
    if (arr[min_arr[end]] > arrI) {
      min_arr[end] = i
      if (end > 0) {
        prev_arr[i] = min_arr[end - 1] // 前面的索引
      }
    }
  }

  // 从最后一项往前查找
  let result = []
  let i = min_arr.length
  let last = min_arr[i - 1]
  while(i-- > 0) {
    result[i] = last
    last = prev_arr[last]
  }

  return result
}

console.log(getSequence([3, 1, 2, 7, 5, 8, 6, 9, 4]))
// 索引:[ 1, 2, 4, 6, 7 ]
// 值: [ 1, 2, 5, 6, 9 ]

zhaowenyin
369 声望22 粉丝

我会修电脑


« 上一篇
vue3的diff算法