二分查找
// 二分查找 首先先学习下
// 一般的二分查找 查下10在数组中的位置
function binarySearch(arr, num) {
let start = 0;
let end = arr.length - 1
while(start<end) {
let mid = Math.floor((start + end) / 2);
if(arr[min] === num) return mid
if(arr[mid]<num) {
start = mid + 1
}else{
end = mid
}
}
}
insterIndex([2, 5, 7, 9, 10, 20], 10)
二分查找 找出第一个比 他大的数
// 这个是查找比他大的第一个数 思路差不多一样 不断缩小范围 最后的 start就是第一个比他大的数 // [2, 5, 7, 9, 10, 20] 现在有个数 11 看看他应该是要插到数组的哪个位置上
function insterIndex(arr, num) {
// 首先就要从 中间开始找 如果中间的数比11小的话 就从右边开始找 继续中间一个数对比
// 否则就是从左边一半 开始
let start = 0
let end = arr.length - 1
while(start<end) {
let mid = Math.floor((start + end) / 2);
if(arr[mid]<num) {
// 中间的数 比11 小 那就说明左边的数都比11小 不用看了 因为是递增的序列
start = mid + 1
}else{
end = mid
}
}
return start
}
insterIndex([2, 5, 7, 9, 10, 20], 11)
最长递增子序列个数
// 接下来要掌握的 就是求最大子序列的 个数了
function getSequenceLength(arr) {
let len = arr.length;
let result = [arr[0]];
for (let i = 1; i < len; i++) {
const arrI = arr[i];
let rsLastNum = result[result.length - 1];
if (rsLastNum < arrI) {
result.push(arrI)
continue;
}
start = 0;
end = result.length - 1
while (start < end) {
middle = (start + end) / 2 | 0;
if (result[middle] < arrI) {
start = middle + 1;
} else {
end = middle
}
}
if (arrI < result[start]) {
result[start] = arrI
}
}
console.log(result)
return result.length
}
let len = getSequenceLength([2, 5, 8, 4, 6, 7, 9, 3]);
// [2, 3, 6, 7, 9] 最后的 result 是这个 虽然不是正确的递增序列 正确的应该是[2, 4, 6, 7, 9] 但是子序列但是个数是正常的
// 这个是一个贪心算法 默认第一个就是最小值
// [2]
// 遍历到 5 5>2 rs = [2, 5]
// 遍历到 8 8>5 rs = [2, 5, 8]
// 遍历到 4 4<8 要利用二分查找找到第一个比4大的数 也就是5 替换他的位置 [2, 4, 8]
// 遍历到 6 6<8 要利用二分查找找到第一个比6大的数 也就是8 替换他的位置 [2, 4, 6]
// 遍历到 7 7>6 rs = [2, 4, 6, 7]
// 遍历到 9 9>7 rs = [2, 4, 6, 7, 9]
// 遍历到 3 3<9 要利用二分查找找到第一个比3大的数 也就是4 替换他的位置 [2, 3, 6, 7, 9]
最长递增子序列个数 但是记录的是下标
// 3 接下来这个是 下标版的 思想都是一样的 只不过是存的是index下标
// 也就是返回的是[2, 3, 6, 7, 9]的下标 [0, 7, 4, 5, 6]
function getSequenceIndex(arr) {
let len = arr.length;
let result = [0];
for (let i = 0; i < len; i++) {
const arrI = arr[i];
resultLastIndex = result[result.length - 1];
if (arr[resultLastIndex] < arrI) {
result.push(i)
continue;
}
start = 0;
end = result.length - 1
while (start < end) {
middle = (start + end) / 2 | 0;
if (arr[result[middle]] < arrI) {
start = middle + 1;
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
result[start] = i
}
}
console.log(result)
return result.length
}
let len2 = getSequenceIndex([2, 5, 8, 4, 6, 7, 9, 3]);
// 先举个通俗易懂的例子 看下
// 不懂的话 看下下面 回头在看下这个例子
// 场景就是 现在一排学生 从里面抽最少的人 让他能够 从低到高排列
// 小明2 小红5 小强6 小溪3 小敏8 小伟4
// 那一个本子记下 let p = [0,0,0,0,0,0]
// 结果 let rs = [小明2]
// 现在开始遍历 下标为1 是小红5
// 小红比小明高 rs = [小明2, 小红5]
// 小红现在要知道 他的上一个人是小明 那么记下p p[1] = 小明2 现在p = [0,小明2,0,0,0,0]
// 现在开始遍历 下标为2 是小强6
// 小强比小红高 rs = [小明2, 小红5, 小强6]
// 那小强就记住上一个人是小红 那么记下p p[2] = 小红5 现在p = [0,小明2,小红5,0,0,0]
// 现在开始遍历 下标为3 是小溪3
// 小溪比小强矮 替换小明的位置 rs = [小溪3, 小红5, 小强6]
// 那小溪上一个人没人 那么记下p p[3] = undefined 现在p = [0,小明2,小红5,undefined,0,0]
// 现在开始遍历 下标为4 是小敏8
// 小强比小红高 rs = [小溪3, 小红5, 小强6, 小敏8]
// 那小敏8就记住上一个人是小强 那么记下p p[4] = 小强6 现在p = [0,小明2,小红5,undefined,小强6,0]
// 现在开始遍历 下标为5 是小伟4
// 小伟4比小敏8矮 替换小溪3的位置 rs = [小伟4, 小红5, 小强6, 小敏8]
// 那小伟4上一个人没人 那么记下p p[5] = undefined 现在p = [0,小明2,小红5,undefined,小强6,undefined]
// 遍历完了现在
// 但是现在的队伍不一定正确 但是最后一个人位置是肯定正确的 也就是说小敏位置绝对是最后一个
// 实在不行在拿一个本子记下
// rs2 = [小敏8]
// 我们知道小敏的下标是4 从p中寻找p[4] = 小强6
// rs2 = [小强6, 小敏8]
// 然后小强6的小标是 2 从p中寻找p[2] = 小红5
// rs2 = [小红5, 小强6, 小敏8]
// 然后小红5的小标是 1 从p中寻找p[1] = 小明2
// rs2 = [小明2, 小红5, 小强6, 小敏8]
// 这就是最终的排序
// 从[小伟4, 小红5, 小强6, 小敏8] 到 [小明2, 小红5, 小强6, 小敏8]
最长递增子序列完整版
function getSequence(arr) {
let len = arr.length;
let result = [0];
let resultLastIndex;
let start;
let end;
let middle;
let p = arr.slice(0); // 用来标识索引的
for (let i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {// 这个是vue中的 特殊处理 0可以不管
resultLastIndex = result[result.length - 1];
if (arr[resultLastIndex] < arrI) {
result.push(i)
p[i] = resultLastIndex; // 让当前最后一项记住前一项的索引
continue;
}
start = 0;
end = result.length - 1
while (start < end) {
middle = (start + end) / 2 | 0;
if (arr[result[middle]] < arrI) {
start = middle + 1;
} else {
end = middle
}
}
if (arrI < arr[result[start]]) {
result[start] = i
p[i] = result[start - 1]; // 记住换的那个人的前一项的索引
}
}
}
// 追溯
let i = result.length;// 获取数组长度
let last = result[i - 1]; // 最后一项的索引
while (i-- > 0) {
result[i] = last; // 用最后一项的索引来追溯
last = p[last]; // 用p中的索引来进行追溯
}
return result
}
let result = getSequence([2, 5, 8, 4, 6, 7, 9, 3]);
// 2, 5, 8, 4, 6, 7, 9, 3
// result 是来记录结果
// p是用来追溯的
// 第一次 循环 i = 0
// 遍历的是 2 当前的result [0] result[length-1] 也是等于2 第一次的时候 没做啥处理 走个场子
// 第二次 循环 i = 1
// 遍历的是 5 5 比 result[length-1]大 也就是5比2大
// result 变成了 [0, 1]
// p 记录下 上一个老大的位置 p[i] = 0 也就是2的位置 [2,0,8,4,6,7,9,3]
// 第三次 循环 i = 2
// 遍历的是 8 8 比 result[length-1]大 也就是8比5大
// result 变成了 [0, 1, 2]
// p 记录下 上一个老大的位置 p[i] = 1 也就是之前5的位置 [2,0,1,4,6,7,9,3]
// 第四次 循环 i = 3
// 遍历的是 4 4 比 result[length-1]小 也就是4比8小
// 4 要从 result里面利用二分法查找 当然result是索引 真正的值在arr这边 找到第一个比4大的值 替换成4的索引
// result 记录下4的位置 [0, 3, 2]
// p记录下4前面一个人的索引 3是4目前的位置 那么他前面的索引就是0 p变成 [2, 0, 1, 0, 6, 7, 9, 3]
// 第五次 循环 i = 4
// 遍历的是 6 6 比 result[length-1]小 也就是6比8小
// 6 要从 result里面利用二分法查找 当然result是索引 真正的值在arr这边 找到第一个比6大的值 替换成6的索引
// result 记录下4的位置 [0, 3, 4]
// p记录下6前面一个人的索引 4是6目前的位置 那么他前面的索引就是3 p变成 [2, 0, 1, 0, 3, 7, 9, 3]
// 第六次 循环 i = 5
// 遍历的是 7 7 比 result[length-1]大 也就是7比6大
// result 变成了 [0, 3, 4, 5]
// p 记录下 上一个老大的位置 p[i] = 4 [2, 0, 1, 0, 3, 4, 9, 3]
// 第七次 循环 i = 6
// 遍历的是 9 9 比 result[length-1]大 也就是9比7大
// result 变成了 [0, 3, 4, 5, 6]
// p 记录下 上一个老大的位置 p[i] = 5 [2, 0, 1, 0, 3, 4, 5, 3]
// 第八次 循环 i = 7
// 遍历的是 3 3 比 result[length-1]小 也就是3比9小
// 3 要从 result里面利用二分法查找 当然result是索引 真正的值在arr这边 找到第一个比3大的值 替换成3的索引
// result 记录下4的位置 [0, 7, 4, 5, 6]
// p记录下3前面一个人的索引 7是3目前的位置 那么他前面的索引就是0 p变成 [2, 0, 1, 0, 6, 7, 9, 0]
// 现在到了 最后的 回溯
// 不管怎么说中间的顺序如何变化 但是最大的那个人 一定是最后一个 而且每个人都记住他前一个人的位置 所以按这样找就能梳理出正确的顺序
// 现在最后的一项 索引是 6 result[4] = 6 [0, 7, 4, 5, 6]
// 从p中 查找 p[6] = 5 得知6的上一个是5
// result[3] = 5 [0, 7, 4, 5, 6]
// 从p中 查找 p[5] = 4 得知5的上一个是4
// result[2] = 4 [0, 7, 4, 5, 6]
// 从p中 查找 p[4] = 3 得知4的上一个是3
// result[1] = 3 [0, 3, 4, 5, 6]
// 从p中 查找 p[3] = 0 得知3的上一个是0
// result[0] = 0 [0, 3, 4, 5, 6]
// 循环结束