例子: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对比
思路:
- 如果当前n比min_arr最后一项大,则直接放到末尾,如:i = 3, n = 7,7比2大,7直接放在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 ]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。