二分查找的思路很简单,就不叙述了,考的也不是思路,重点是应用。
二分查找不止可以用于有序数组,在特定情况下也可以用于无序数组。
在无序数组的情况下应用
题目要求:
给定一个无序数组arr,已知任意相邻的两个元素值都不重复。请返回任意一个局部最小的位置。
所谓局部最小的位置是指:
- 如果arr[0]<arr[1],那么位置0就是一个局部最小的位置。
- 如果ar[N-1](也就是arr最右的数)小于arr[N-2],那么位置N-1也是局部最小的位置。
- 如果位置i既不是最左位置也不是最右位置。那么只要满足arr[i]同时小于它左右两侧的值即(arr[i-1]和arr[i+1]),那么位置i也是一个局部最小的位置。
题目解析:
本题是找局部最小,不是找最小值。也就是说,你只要找到某一部分的最小值就行了。根据题目要求我自己来出几组例子:
输入样例 | 输出样例 |
---|---|
[1,2,3,1,2,3] | 1 |
[3,2,3,1,2,0] | 0 |
[6,2,3,-5,2,3] | 2(3左边的2)或者-5 |
[6,4,3,1,0,3] | 0 |
上边第三组样例输出几取决于你的代码。按照我的代码来那就是输出2。因为找的是局部最小。当中间值比两边都大的时候,你返回任意一边就可以了,我是默认返回左边的。
function min(a) {
let length = a.length
if (length === 0) { //数组空
return -1
} else {
let low = 0
let high = length - 1
let mid
if(low === high || a[0] < a[1]){
//数组中只有一个数或者arr[0]<arr[1],a[0]就是局部最小
return 0
}else if (a[length - 1] < a[length - 2]) {
return length - 1
} else {
while (low <= high) { //二分法
mid = Math.floor(low + (high - low) / 2) //向下取整
if (a[mid] < a[mid + 1] && a[mid] < a[mid - 1]) {
return mid
} else if (a[mid] > a[mid - 1]) {
high = mid - 1
} else {
low = mid + 1
}
}
}
}
}
补充:代码里我用的low + (high - low) / 2
而不是(low + high)/2
因为这样可以防止数组过大时发生溢出。
在有序重复数组中的应用
1
给定一个有序数组arr,再给定一个整数num请在arr中找到num这个数出现的最左边的位置。
题目要求:
function find(a, num) {
let res = -1
if (a.length === 0) { //数组为空
return res
}
let right = a.length - 1
let mid
let left = 0
while (left <= right) {
if (a[0] === num) { //a[0]就是要找的数
return res = 0
} else {
while (left <= right) {
mid = Math.floor(left + (right - left) / 2)
if (a[mid] === num && a[mid] > a[mid - 1]) {
res = mid
break //一定要写,否则死循环
} else if (a[mid] < num) {
left = mid + 1
} else {
right = mid - 1
}
}
return res
}
}
}
2
给定一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
function find(arr) {
let res = -1
if (arr.length === 0 || arr[0] > arr[length - 1] || arr[length - 1] < 0) {
//长度为0 或者 arr[0]>arr[length - 1]数组递增所以不可能出现arr[i]=i
return res
} else {
let left = 0,
right = arr.length - 1,
mid = left + (right - left) / 2
while (left <= right) {
mid = Math.trunc(left + (right - left) / 2)
if (arr[mid] < mid) {
left = ++mid
} else if (arr[mid] > mid) {
right = --mid
} else {
res = mid
right = --mid
}
}
return res
}
}
循环数组找最值/中位数
1
题目要求:给定一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组1,2,3,3,4,是有序循环数组,4,1,2,3,3也是。
输入样例 | 输出样例 |
---|---|
0,1,2,3,-1,-1,-1 | -1(res=4) |
0 | 0 |
2,2,2,2,1,2,2,2 | 1 |
function Min(arr) {
//我这里res仅仅是记录下标而已,你可以不写的。
let res = -1;
if (arr.length === 0) {
return res //数组空。查找失败
} else if (arr.length === 1 || arr[0] < arr[arr.length - 1]) {
//数组中一个值 或者 第一个小于最后一个即没翻转过
return arr[0]
} else {
let left = 0,
right = arr.length - 1,
mid = 0
while (left <= right) {
mid = Math.trunc(left + (right - left) / 2)
if (left + 1 === right) {
res = arr[left] > arr[right] ? right : left
return arr[res]
} else {
if (arr[left] > arr[mid]) {
right = mid
} else if (arr[mid] > arr[right]) {
left = mid
} else if (arr[left] === arr[mid] && arr[mid] === arr[right]) {
let min = arr[0]
for (let i = 1; i < arr.length; i++) {
//这是为了应对输入样本3,遍历查找
if (arr[i] < arr[0]) {
res = i
return arr[res]
}
}
}
}
}
}
}
2
还是这个题。如果让你找中位数呢?
function Min(arr) {
let res = "数组空,无中位数";
let m = Math.trunc(arr.length/2)
var num = function(res){
if (arr.length % 2 !== 0) {
res = arr[(res + m) % arr.length]
} else {
num1 = arr[(res + m) % arr.length]
num2 = arr[(res + m -1 ) % arr.length]
res = (num1 + num2) / 2
}
return res
}
if (arr.length === 0) {
return res //数组空。查找失败
} else if (arr.length === 1) { //数组中一个值
return arr[0]
} else if(arr[0] < arr[arr.length - 1]){ //第一个小于最后一个即没翻转过
return num(0)
}else {
let left = 0,
right = arr.length - 1,
mid = 0
while (left <= right) {
mid = Math.trunc(left + (right - left) / 2)
if (left + 1 === right) {
res = num(arr[left] > arr[right] ? right : left)
return res
} else {
if (arr[left] > arr[mid]) {
right = mid
} else if (arr[mid] > arr[right]) {
left = mid
} else if (arr[left] === arr[mid] && arr[mid] === arr[right]) {
let min = arr[0]
for (let i = 1; i < arr.length; i++) {
//这是为了应对输入样本3,遍历查找
if (arr[i] < arr[0]) {
res = num(a[i])
return arr[res]
}
}
}
}
}
}
}
应用:更快的求一个整数k的N次方。
如果两个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程k*k*…*k
复杂度为O(n),请想办法实现时间复杂度为O(logN)的方法。
这个也是用的二分查找的思想,虽然我没看出来。但是不得不说算法博大精深!!!
function calculate(a, b) {
let arr = []
//将b转换为2进制表示
while (b != 0) {
arr.push(b % 2)
b = Math.trunc(b / 2)
}
console.log(arr)
let mul = a
let res
if(arr[0]!==0){
res = a
}else{
res = 1
}
//b二进制有几位,乘几次就可以了
for (let i = 1; i < arr.length; i++) {
mul *= mul
console.log("mul: "+mul)
if (arr[i] === 1) {
res *= mul
console.log(res)
}
}
}
题目来源于牛客网面试算法。