二分查找是一种效率较高的查找方法,前提是数据结构必须先排好序,它的平均时间复杂度是O(logn)。碰到排好序的数组,第一时间应该想到用二分法求解。
二分查找的递归与非递归实现
- 在有序数组arr中,查找目标值value的元素下标
// 递归
function biSearch(arr, start, end, value) {
if (start > end) return -1
let mid = Math.floor((start + end) / 2)
if (arr[mid] < value) {
return biSearch(arr, mid + 1, end, value)
} else if (arr[mid] > value) {
return biSearch(arr, start, mid - 1, value)
} else {
return mid
}
}
// 非递归
function biSearch(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] === value) {
return mid
}
if (arr[mid] < value) {
start = mid + 1
} else {
end = mid - 1
}
mid = (start + end) >> 1
}
return -1
}
二分查找变形问题
- 在有序数组中,查找第一个等于给定值的元素的下标
// 遍历
function getFirstOne(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] > value) {
end = mid - 1
} else if (arr[mid] < value) {
start = mid + 1
} else {
if (mid === start || arr[mid - 1] !== value) {
return mid
} else {
end = mid - 1
}
}
mid = (start + end) >> 1
}
return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 10
let result = getFirstOne(arr, value)
console.log('result', result)
// 递归
// 递归
function getFirstOne(arr, start, end, value) {
if (start > end) return -1
let mid = (start + end) >> 1
if (arr[mid] > value || (mid !== start && arr[mid-1] === value)) {
return getFirstOne(arr, start, mid - 1, value)
} else if (arr[mid] < value) {
return getFirstOne(arr, mid + 1, end, value)
} else {
return mid
}
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getFirstOne(arr, 0, arr.length - 1, value)
console.log('result', result)
- 在有序数组中,查找最后一个等于给定值的元素的下标
// 遍历
function getLastOne(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] < value) {
start = mid + 1
} else if (arr[mid] > value) {
end = mid - 1
} else {
if (mid === end || arr[mid+1] !== value) {
return mid
} else {
start = mid + 1
}
}
mid = (start + end) >> 1
}
return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getLastOne(arr, value)
console.log('result', result)
// 递归
function getLastOne(arr, start, end, value) {
if (start > end) return -1
let mid = (start + end) >> 1
if (arr[mid] < value) {
return getLastOne(arr, mid + 1, end, value)
} else if (arr[mid] > value) {
return getLastOne(arr, start, mid - 1, value)
} else {
if (mid === end || arr[mid + 1] !== value) {
return mid
} else {
return getLastOne(arr, mid + 1, end, value)
}
}
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getLastOne(arr, 0, arr.length - 1, value)
console.log('result', result)
- 在有序数组中,查找第一个大于等于给定值的元素的下标
// 遍历
function getFirstMaxOne(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] < value) {
start = mid + 1
} else {
if (arr[mid - 1] < value || mid === start) {
return mid
}
end = mid - 1
}
mid = (start + end) >> 1
}
return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 6
let result = getFirstMaxOne(arr, value)
console.log('result', result)
// 递归
function getFirstMaxOne(arr, start, end, value) {
if (start > end) return -1
let mid = (start + end) >> 1
if (arr[mid] < value) {
return getFirstMaxOne(arr, mid + 1, end, value)
} else {
if (arr[mid-1] < value || mid === start) {
return mid
}
return getFirstMaxOne(arr, start, mid - 1, value)
}
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 2
let result = getFirstMaxOne(arr, 0, arr.length - 1, value)
console.log('result', result)
- 在有序数组中,查找最后一个小于等于给定值的元素的下标
// 遍历
function getFirstMaxOne(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] < value) {
start = mid + 1
} else {
if (arr[mid - 1] < value || mid === start) {
return mid
}
end = mid - 1
}
mid = (start + end) >> 1
}
return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 6
let result = getFirstMaxOne(arr, value)
console.log('result', result)
// 递归
function getLastMinOne(arr, value) {
let start = 0
let end = arr.length - 1
let mid = (start + end) >> 1
while (start <= end) {
if (arr[mid] > value) {
end = mid - 1
} else {
if (arr[mid + 1] > value || mid === end) {
return mid
}
start = mid + 1
}
mid = (start + end) >> 1
}
return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 2
let result = getLastMinOne(arr, value)
console.log('result', result)
其它经典试题
- 在有序的稀疏数组中,查找目标值
function findStr(words, s) {
let start = 0
let end = words.length - 1
let mid = (start + end) >> 1
while (start <= end) {
// 遇到特殊字符,退化成线性
while (words[mid] === '' && mid > start) {
mid = mid - 1
}
if (words[mid] === s) {
return mid
}
if (words[mid] > s) {
end = mid - 1
} else {
start = mid + 1
}
mid = (start + end) >> 1
}
return -1
}
- 在有序的数组中,查找确实的数字
// 缺失数字
function missNum(nums) {
let start = 0
let end = nums.length - 1
while (start <= end) {
let mid = (start + end) >> 1
if (nums[mid] <= mid) {
start = mid + 1
} else {
if (nums[mid-1] === mid - 1 || mid === start) {
return mid
}
end = mid - 1
}
}
return nums.length
}
let nums = [1,2,3,4,5,6,7]
let result = missNum(nums)
console.log('result', result)
- 在有序的数组中,统计目标值出现的次数
function search(nums, target) {
let start = 0
let end = nums.length - 1
let times = 0
while (start <= end) {
let mid = (start + end) >> 1
let temp = mid
if (nums[mid] < target) {
start = mid + 1
} else if (nums[mid] > target) {
end = mid - 1
} else {
// 左边统计
while (temp >= start && nums[temp] === target) {
times++
temp--
}
// 右边统计
temp = mid + 1
while(temp <= end && nums[temp] === target) {
times++
temp++
}
break
}
}
return times
}
let nums = [9, 9, 10, 11, 12, 12, 12, 12]
let result = search(nums, 12)
console.log('result', result)
- 查找第K小的元素
leetcode378
// 查找第K小的元素,二维矩阵 O(nlogn)
function fn2(matrix, k) {
let n = matrix.length
if (k < 0 || k > n * n) return null
let start = matrix[0][0]
let end = matrix[n-1][n-1]
while (start < end) {
let mid = (start + end) >> 1
let num = getNum(matrix, mid)
if (num >= k) {
end = mid
} else {
start = mid + 1
}
}
return start
}
// 二维矩阵中,获取小于目标值的数量 O(n)
function getNum(matrix, value) {
let n = matrix.length
let i = n - 1
let j = 0
let num = 0
while(i >= 0 && j < n) {
if (matrix[i][j] <= value) {
num = num + i + 1
j++
} else {
i--
}
}
return num
}
总结
二分查找问题很常见,碰到关键字有序数组,时间复杂度O(logn),就要想到用二分查找;具体实现可以用遍历或者递归,多练习就能熟能生巧。