二分法是一种快速查找算法,标准二分查找算法时间复杂度lg(n)。下面我们通过一些例子来进行二分查找算法的解读。
leetcode的704题:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/binary-search/xexoac/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这题是标准的二分查找算法,大家估计都清楚,给定非空有序数组array(假设是升序)与寻找的目标target,主要思路如下:
1、两个指针,左右指针left、right,其中将left指针指向位置0,right指针指向末尾len(array) - 1
2、确定中间指针mid,mid = left + (right - left) / 2
3、然后判断array[mid] 与 target 的大小,有3中情况:
- 相等,此时直接返回mid就好
- 如果 array[mid] > target,说明此时mid所在位置的数比 target 要大,此时,right移动到mid
- 如果 array[mid] < target,说明此时mid所在位置的数比 target 要小,此时,left移动到mid
4、重复以上步骤直到left > right 或者找到target为止
Java代码和Python代码分别如下:
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
if (nums[0] > target || nums[nums.length -1] < target) {
return -1;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
return -1;
}
}
class Solution:
def search(self, nums: List[int], target: int) -> int:
if nums is None or len(nums) == 0:
return -1
if nums[0]>target or nums[-1] < target:
return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid-1
else:
left = mid+1
return -1
int binarySearch(vector& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size() - 1;
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
// End Condition: left > right
return -1;
}
int binarySearch(int[] nums, int target){
if(nums == null || nums.length == 0)
return -1;
int left = 0, right = nums.length - 1;
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
// End Condition: left > right
return -1;
}
def binarySearch(nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
# End Condition: left > right
return -1
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题目相对比较简单和基础,直接套用上述模版就好了。
class Solution:
def search(self, nums: List[int], target: int) -> int:
if nums is None or len(nums) == 0:
return -1
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
这道题目数组是有限长度了,如果数组是无限长度的呢?
那么请看下一道题。
给定一个升序整数数组,写一个函数搜索 nums 中数字 target。如果 target 存在,返回它的下标,否则返回 -1。注意,这个数组的大小是未知的。你只可以通过 ArrayReader 接口访问这个数组,ArrayReader.get(k) 返回数组中第 k 个元素(下标从 0 开始)。
你可以认为数组中所有的整数都小于 10000。如果你访问数组越界,ArrayReader.get 会返回 2147483647。
样例 1:
输入: array = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 存在在 nums 中,下标为 4
样例 2:
输入: array = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不在数组中所以返回 -1
注释 :
你可以认为数组中所有元素的值互不相同。
数组元素的值域是 [-9999, 9999]。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-a-sorted-array-of-unknown-size
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题目其实相当于是将有限数组变成了无限长度数组,解题思路类似,如果遍历查找时注意剪枝。
假设一种场景:
如果说,我们要求的目标数不在这个无限数组中,那么可以直接剪枝掉返回-1,否则会一直寻找下去造成超时。
时间复杂度O(n),空间复杂度O(1)
# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
# def get(self, index: int) -> int:
class Solution:
def search(self, reader, target):
"""
:type reader: ArrayReader
:type target: int
:rtype: int
"""
i = 0
if reader.get(i) == 2147483647:
return -1
while reader.get(i) != target:
if reader.get(i) == 2147483647:
return -1
i += 1
return i
这里二分查找的思路相对难找一些,这里总结一下几个方法。
我们知道,二分查找离不开两个条件:一是数组有序,另一个是依据mid与目标值的大小关系来确定左右边界。
目前,数组有序这个条件已经满足了。接下来,需要创造依据mid与目标值的大小关系来确定左右边界的条件。满足这个条件需要有初始边界就好。
该数组是无限数组,如何创造边界呢?题目中有两个假设条件:
注释 :
2147483647
,超过数组元素最大数值了,因此,即使越界也可以直接进行比较。# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
# def get(self, index: int) -> int:
class Solution:
def search(self, reader, target):
"""
:type reader: ArrayReader
:type target: int
:rtype: int
"""
left, right = 0, 19999
if reader.get(0) == 2147483647:
return -1
while left <= right:
mid = left + (right - left) // 2
if reader.get(mid) == target:
return mid
elif reader.get(mid) < target:
left = mid + 1
else:
right = mid-1
return -1
由上一条分析,主要是为了创造比较的条件。
那么,无限数组怎么变成有限的呢?可以用区间长度为1的区间(即元素)进行比较,区间为左闭右开区间。
此时,解法更泛化一些,保证数组有序就好了。
这里有两种写法:
# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
# def get(self, index: int) -> int:
class Solution:
def search(self, reader, target):
"""
:type reader: ArrayReader
:type target: int
:rtype: int
"""
left, right = 0, 1
if reader.get(0) == 2147483647:
return -1
while reader.get(right) < target:
left = right
right = right * 2
while left <= right:
mid = left + (right - left) // 2
if reader.get(mid) == target:
return mid
elif reader.get(mid) < target:
left = left + 1
else:
right = right - 1
return -1
# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
# def get(self, index: int) -> int:
class Solution:
def search(self, reader, target):
"""
:type reader: ArrayReader
:type target: int
:rtype: int
"""
left, right = 0, 1
if reader.get(0) == 2147483647:
return -1
while left <= right:
mid = left + (right - left) // 2
if reader.get(mid) == target:
return mid
elif reader.get(mid) < target:
left = left + 1
right = right * 2
else:
right = right - 1
return -1