算法思路:
循环条件start + 1 < end。 当跳出循环时,start和end的关系只能是相等或相邻。 相等是若数组只有一个元素,没有进入循环时出现。当进入过循环,一定是相邻。
跳出循环后比较start和end的关系从而判断答案。
这可以满足二分法找first position或者last position, peak element的题目。 first position中若等于target,end = mid,因为要在左半部分找,相反last position在右半部分找,所以start = mid。
应用:
有序数组找目标
没给定目标情况下,找峰值, 缺失数(元素间关系)
没给定目标情况下,求第k小的数,如求根号(数值关系)
找目标 注意事项:
判断数组是否为空。
如果只有唯一的target的话,target==nums[mid]可以并入任何一种。end和target的顺序也没关系。其他注意循环后要根据题目条件(如小于或者大于或者等于tgt), 再比较一次start和end上的元素,详见下表
类型
if target = nums[mid]
循环之后
备注
binary_search
任意
任意
N/A
last_position
向右start = mid
先end且是否等于tgt
贪婪法,找最后一个target,所以尽量靠后,先end
first_position
向左end = mid
先start是否等于tgt
贪婪法,找第一个target,所以尽量靠前,先start
smaller(_or_equal)_position
向左end = mid
先end是否小于tgt
贪婪法,找最后一个小于target的数,所以尽量靠近target,先end
greater(_or_equal)_position
向右start = mid
先start是否大于tgt
贪婪法,找第一个大于target的数,所以尽量靠近target,先start
Python代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def binary_search (self, nums: List [int ], target: int ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if target < nums[mid]: end = mid else : start = mid if nums[end] == target: return end elif nums[start] == target: return start else : return -1
Python代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def last_position (self, nums: List [int ], target: int ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if target < nums[mid]: end = mid elif target > nums[mid]: start = mid else : start = mid if nums[end] == target: return end elif nums[start] == target: return start else : return -1
Python代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def first_position (self, nums: List [int ], target: int ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if target < nums[mid]: end = mid elif target > nums[mid]: start = mid else : end = mid if nums[start] == target: return start elif nums[end] == target: return end else : return -1
Python代码: 如果是smaller_or_equal_position,Line 13和15取等号1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def smaller_position (self, nums: List [int ], target: int ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if target > nums[mid]: start = mid elif target < nums[mid]: end = mid else : end = mid if nums[end] < target: return end if nums[start] < target: return start return -1
Python代码: 如果是greater_or_equal_position,Line 13和15取等号1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def greater_position (self, nums: List [int ], target: int ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if target > nums[mid]: start = mid elif target < nums[mid]: end = mid else : start = mid if nums[start] > target: return start if nums[end] > target: return end return -1
找峰值 注意事项:
判断mid+1的元素不越界
最后返回start和end之中较大者
Python代码: 1 2 3 4 5 6 7 8 9 10 11 def find_peak (self, nums: List [int ] ) -> int : if not nums: return -1 start, end = 0 , len (nums) - 1 while start + 1 < end: mid = start + (end - start) // 2 if mid + 1 <= end and nums[mid] < nums[mid + 1 ]: start = mid else : end = mid return start if nums[start] > nums[end] else end
找第k小的数 注意事项:
数值比较,所以mid是除2获得不是//2。start,end,mid都是数值而不是索引
k是从0开始,count==k的时候,k在mid的右边,如数组1-10, k=3, mid=3.5, count=3,k是前4个数,所以在mid右边。nums[i] <= mid这个等号有没有也没关系,因为mid是小数,不会相等的。
最后start,end区间内是一个整数正是所求,所以返回end向下取整
Python代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import mathdef binary_select (self, nums: List [int ], k: int ) -> int : if not nums: return -1 start, end, epsilon = min (nums), max (nums), 0.5 while end - start > epsilon: mid = start + (end - start) / 2 count = len ([n for n in nums if n <= mid]) if k < count: end = mid elif k > count: start = mid else : start = mid return int (end)
若nums有序,count的统计可以用bisect(nums, mid)完成 ,记得不需要用+1,而且空数组也无问题 上面line 8可以改成1 count = bisect.bisect(matrix[i], mid)
算法分析: 时间复杂度为O(nlog(|hi-lo|/delta)),如果数据比较平均也就是差值相当的情况下(比如1,2,3),复杂度为O(nlogn). 空间复杂度O(1)。 若不需要搜索全数组,前面的n会变成logn
Java代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public int lastPosition (int [] nums, int target) { if (nums == null || nums.length == 0 ) return -1 ; int start = 0 , end = nums.length - 1 ; while (start + 1 < end) { int mid = start + (end - start) / 2 ; if (nums[mid] < target) start = mid; else if (nums[mid] == target) start = mid; else end = mid; } if (nums[end] == target) return end; if (nums[start] == target) return start; return -1 ; }
算法分析: 时间复杂度为O(logn),空间复杂度O(1)。