算法之二分搜索
经典二分搜索法:
先找到目标值的第一个位置,如果没找到则返回-1
最后一个位置以及其他位置怎么处理?
找第一个位置:将==条件成立改为right = mid
找最后一个位置:将==条件成立改为left = mid
当遇到一个数组的时候→
先想 这个数组是不是排好序的
再看 这个数组里面有没有重复的数字,如果有,是找第几个
再看 这个数组里面有没有负数
一、找目标值的第一个位置/最后一个位置/一个区间/第一个大于等于目标值/最后一个小于等于目标值
Find 1st position of target, return -1 if not found
How about last position, any position?
1 def binarysearch(nums, item): 2 if len(nums) == 0: 3 return -1 4 left, right = 0, len(nums) - 1 5 while left + 1 < right: 6 # 跳出循环体: LR相邻的时候, LR相同的时候 7 mid = left + (right - left) // 2 8 if nums[mid] == item: 9 right = mid 10 elif nums[mid] < item: 11 left = mid 12 else: 13 right = mid 14 if nums[left] == item: 15 return left 16 if nums[right] == item: 17 return right 18 return -1
进阶:找一个区间
1 def search_range(alist, target): 2 if len(alist) == 0: 3 return (-1, -1) 4 lbound, rbound = -1, -1 5 # search for left bound 6 left, right = 0, len(alist) - 1 7 while left + 1 < right: 8 mid = left + (right - left) // 2 9 if alist[mid] == target: 10 right = mid 11 elif (alist[mid] < target): 12 left = mid 13 else: 14 right = mid 15 if alist[left] == target: 16 lbound = left 17 elif alist[right] == target: 18 lbound = right 19 else: 20 return (-1, -1) 21 # search for right bound 22 left, right = 0, len(alist) - 1 23 while left + 1 < right: 24 mid = left + (right - left) // 2 25 if alist[mid] == target: 26 left = mid 27 elif (alist[mid] < target): 28 left = mid 29 else: 30 right = mid 31 if alist[right] == target: 32 rbound = right 33 elif alist[left] == target: 34 rbound = left 35 else: 36 return (-1, -1) 37 return (lbound, rbound)
Java版本
1 package my0511; 2 3 public class BinarySearch1 { 4 public static void main(String[] args) { 5 6 } 7 //查找第一个等于目标值的元素 8 public int binarysearch_first(int[] a, int n, int value){ 9 int low = 0; 10 int high = n - 1; 11 while (low <= high){ 12 int mid = low + ((high - low) >> 1); 13 if (a[mid] > value){ 14 high = mid - 1; 15 } else if (a[mid] < value){ 16 low = mid + 1; 17 } else { 18 if ((mid == 0) || (a[mid - 1] != value)) return mid; 19 else high = mid - 1; 20 } 21 } 22 return -1; 23 } 24 //查找最后一个等于目标值的元素 25 public int binarysearch_last(int[] a, int n, int value){ 26 int low = 0; 27 int high = n - 1; 28 while (low <= high){ 29 int mid = low + ((high - low) >> 1); 30 if (a[mid] > value){ 31 high = mid - 1; 32 } else if (a[mid] < value){ 33 low = mid + 1; 34 } else { 35 if ((mid == n - 1) || (a[mid - 1] != value)) return mid; 36 else low = mid + 1; 37 } 38 } 39 return -1; 40 } 41 //查找第一个大于等于目标值的元素 42 public int binarysearch_firstbigoreq(int[] a, int n, int value){ 43 int low = 0; 44 int high = n - 1; 45 while (low <= high){ 46 int mid = low + (high - low) >> 1; 47 if (a[mid] >= value){ 48 if ((mid == 0) || (a[mid - 1] < value)) return mid; 49 else high = mid - 1; 50 } else { 51 low = mid + 1; 52 } 53 } 54 return -1; 55 } 56 //查找最后一个小于等于目标值的元素 57 public int binarysearch_lastsmalloreq(int[] a, int n, int value){ 58 int low = 0; 59 int high = n - 1; 60 while (low <= high){ 61 int mid = low + (high - low) >> 1; 62 if (a[mid] > value){ 63 high = mid - 1; 64 } else { 65 if ((mid == n - 1) || (a[mid + 1] > value)) return mid; 66 else low = mid + 1; 67 } 68 } 69 return -1; 70 } 71 }
二、在旋转有序数列中查找最小值****************
Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. Find the minimum element.
思路一:min O(n)
思路二:排序 O(nlogn)
思路三:二分,用left与mid相比,看是否左半边是否是有序的
用left与mid相比,
left、mid、right,找拐点
一半有拐点,一段排好序
logn复杂度的解法只有二分法
1 def search(nums): 2 if len(nums) == 0: 3 return -1 4 left, right = 0, len(nums) - 1 5 while left + 1 < right: 6 if nums[left] < nums[right]: 7 return nums[left] 8 mid = left + (right - left) // 2 9 if nums[mid] >= nums[left]: 10 left = mid + 1 11 else: 12 right = mid 13 return nums[left] if nums[left] < nums[right] else nums[right]
进阶:在旋转数组中查找
思路:在有序部分查找
1 def search(alist, target): 2 if len(alist) == 0: 3 return -1 4 left, right = 0, len(alist) - 1 5 while left + 1 < right: 6 mid = left + (right - left) // 2 7 if alist[mid] == target: 8 return mid 9 if (alist[left] < alist[mid]): 10 if alist[left] <= target and target <= alist[mid]: 11 right = mid 12 else: 13 left = mid 14 else: 15 if alist[mid] <= target and target <= alist[right]: 16 left = mid 17 else: 18 right = mid 19 if alist[left] == target: 20 return left 21 if alist[right] == target: 22 return right 23 return -1
进阶:在有重复元素的旋转排序数组中查找
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
输入: nums = [2,5,6,0,0,1,2]
, target = 0
输出: true
重复元素,影响了判断拐点和有序部分,故只需要去除直到能找到拐点区和有序区即可。
1 class Solution: 2 def search(self, nums: List[int], target: int) -> bool: 3 if len(nums) == 0: 4 return False 5 left = 0 6 right = len(nums) - 1 7 while left + 1 < right: 8 mid = (left + right) // 2 9 if nums[mid] == target: 10 return True 11 if nums[left] == nums[mid]: 12 left += 1 13 continue 14 if nums[mid] >= nums[left]: 15 if nums[mid] >= target and target >= nums[left]: 16 right = mid 17 else: 18 left = mid 19 else: 20 if nums[mid] <= target and target <= nums[right]: 21 left = mid 22 else: 23 right = mid 24 if nums[left] == target: 25 return True 26 if nums[right] == target: 27 return True 28 return False
进阶:在有重复元素的旋转排序数组中查找最小值*************
1 class Solution: 2 def findMin(self, nums: List[int]) -> int: 3 left, right = 0, len(nums) - 1 4 while left < right: 5 mid = (left + right) // 2 6 if nums[mid] > nums[right]: left = mid + 1 7 elif nums[mid] < nums[right]: right = mid 8 else: right = right - 1 # key 9 return nums[left]
1 class Solution: 2 def findMin(self, nums: List[int]) -> int: 3 left, right = 0, len(nums) - 1 4 while left + 1 < right: 5 mid = (left + right) // 2 6 if nums[mid] == nums[right]: 7 right -= 1 8 continue 9 if nums[mid] < nums[right]: 10 right = mid 11 elif nums[mid] > nums[right]: 12 left = mid 13 return min(nums[left], nums[right])
问题:通过但感觉有问题,多次旋转?
搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个。
1 class Solution: 2 def search(self, arr: List[int], target: int) -> int: 3 if len(arr) == 0: 4 return -1 5 left = 0 6 right = len(arr) - 1 7 while left + 1 < right: 8 mid = (left + right) // 2 9 if arr[left] == target: 10 return left 11 if arr[mid] == target: 12 right = mid 13 elif arr[left] == arr[mid]: 14 left += 1 15 continue 16 elif arr[mid] < arr[left]: 17 if arr[mid] < target and target <= arr[right]: 18 left = mid 19 else: 20 right = mid 21 elif arr[mid] > arr[left]: 22 if arr[mid] > target and arr[left] <= target: 23 right = mid 24 else: 25 left = mid 26 if arr[left] == target: 27 return left 28 if arr[right] == target: 29 return right 30 return -1
三、搜索插入位置
bisect:找到返回索引;没找到,返回应该插入的位置
Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. You may assume no duplicates in the array
1 #本质是找到第一个大于等于目标值的索引 2 def bisect_prac(nums, target): 3 if len(nums) == 0: 4 return 0 5 left = 0 6 right = len(nums) - 1 7 while left + 1 < right: 8 mid = (left + right) // 2 9 if nums[mid] == target: 10 return mid 11 elif nums[mid] < target: 12 left = mid 13 else: 14 right = mid 15 if nums[left] > target: 16 return left 17 if nums[right] > target: 18 return right 19 return right + 1
四、在用空字符串隔开的字符串的有序列中查找
Given a sorted array of strings which is interspersed with empty strings, write a method to find the location of a given string.
1 #最差情况下O(n) 2 #如果往前面找,就要要求前面没有空的字符串,要找到第一个非空字符串#作为left 3 def search_empty(alist, target): 4 if len(alist) == 0: 5 return -1 6 left, right = 0, len(alist) - 1 7 while left + 1 < right: 8 while left + 1 < right and alist[right] == "": 9 right -= 1 10 if alist[right] == "": 11 right -= 1 12 if right < left: 13 return -1 14 mid = left + (right - left) // 2 15 while alist[mid] == "": 16 mid += 1 17 if alist[mid] == target: 18 return mid 19 if alist[mid] < target: 20 left = mid + 1 21 else: 22 right = mid - 1 23 if alist[left] == target: 24 return left 25 if alist[right] == target: 26 return right 27 return -1
五、在无限序列中找到某元素的第一个出现位置
数据流
不知道序列长度
1 def search_first(nums, target): 2 left, right = 0, 1 3 while nums[right] < target: 4 left = right 5 right *= 2 6 if right > len(nums): 7 right = len(nums) - 1 8 break 9 return left + search(nums[left:right+1], 1)[0]
六、供暖
bisect是找到第一个大于等于target的元素
1 import bisect 2 def radius(houses, heaters): 3 heaters.sort() 4 ans = 0 5 for house in houses: 6 hi = bisect(heaters, house) 7 left = heaters[hi-1] if hi - 1 >= 0 else float('-inf') 8 right = heaters[hi] if hi < len(heaters) else float('inf') 9 ans = max(ans, min(house - left, right - house)) 10 return ans
七、sqrt(x)
Implement int sqrt(int x).
Compute and return the square root of x.
x is guaranteed to be a non-negative integer.
1 def sqrt_(x): 2 if x == 0: 3 return 0 4 left, right = 0, x 5 while left + 1 < right: 6 mid = left + (right - left) // 2 7 if mid == x // mid: 8 return mid 9 if mid < x // mid: 10 left = mid 11 else: 12 right = mid 13 if right <= x: 14 return right 15 return left
1 def sqrt(x): 2 if x == 0: 3 return 0 4 left, right = 1, x 5 while left <= right: 6 mid = left + (right - left) // 2 7 if (mid == x // mid): 8 return mid 9 if (mid < x // mid): 10 left = mid + 1 11 else: 12 right = mid - 1 13 return right
八、矩阵搜索
在一个NxM的矩阵里,每一行都是排好序的,每一列也都是排好序的,请设计一个算法在矩阵中查找一个数。
思路一:去每一行二分法,nlogm 去每一列二分法:mlogn
思路二:从右上角或左下角开始,找一个大于或小于只有一个方向的位置
1 def matrixsearch(matrix, target): #O(m+n) 2 m = len(matrix) 3 n = len(matrix[0]) 4 row = 0 5 column = n - 1 6 while row < m and column >= 0: 7 if matrix[row][column] == target: 8 return (row, column) 9 elif matrix[row][column] > target: 10 column -= 1 11 else: 12 row += 1 13 return -1 14 matrix = [[1, 3, 5, 7, 9], 15 [2, 4, 6, 8, 10], 16 [12, 13, 18, 21, 30], 17 [20, 25, 30, 35, 40]] 18 print(matrixsearch(matrix, 400))
矩阵搜索Ⅱ Kth Smallest Element in a Sorted Matrix******
Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix.
Note that it is the kth smallest element in the sorted order, not the kth distinct element.
1 from bisect import bisect 2 def kthSmallest(matrix, k): 3 low, high = matrix[0][0], matrix[-1][-1] 4 while low < high: 5 mid = low + (high - low) >> 1 6 if sum(bisect(row, mid) for row in matrix) < k: 7 low = mid + 1 8 else: 9 high = mid 10 return low
九、找到重复数******
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Note:
You must not modify the array (assume the array is read only).
You must use only constant, O(1) extra space.
Your runtime complexity should be less than O(n2).
There is only one duplicate number in the array, but it could be repeated more than once.
思路:找中间数m,找左边有多少数小于m,右边有多少数大于m
1 def findDuplicate(nums): 2 low = 0 3 high = len(nums) - 1 4 while low < high: 5 mid = (low + high) // 2 6 count = 0 7 for i in nums: 8 if i <= mid: 9 count += 1 10 if count <= mid: 11 low = mid + 1 12 else: 13 high = mid 14 return low
十、地板和鸡蛋
假设有一个100层高的建筑,如果一个鸡蛋从第N层或者高于N层坠落,会摔破。如果鸡蛋从任何低于N层的楼层坠落,则不会破。现在给你两个鸡蛋,请在最少次数下找到N。
思路:一个鸡蛋用于找范围,另一个鸡蛋用于精确定位
n+(n-1)+(n-2)+......+1 > 100 第一次从14层摔,第二次从27层摔
进阶:1000层楼,3个鸡蛋
十一、找两个有序数组的中值
两个数组相同大小
进阶:两个有序数组长度分别为N1和N2,请用logn的方法找到中值
十二、合并区间
给定一个区间的集合,将所有存在交叉范围的区间进行合并。
输入:[[1,3], [2,6], [8,10], [15,18]] 输出:[[1,6], [8,10], [15,18]]
1 def merge(nums): 2 nums.sort(key=lambda x: x[0]) 3 res = [] 4 for i in nums: 5 if not res or res[-1][1] < i[0]: 6 res.append(i) 7 else: 8 res[-1][1] = max(res[-1][1], i[1]) 9 return res 10 print(merge([[1, 3], [2, 6], [8, 10], [15, 18]]))
进阶:插入区间LeetCode57******
给出一个无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
1 #插入+合并 2 def merge(nums): 3 nums.sort(key=lambda x: x[0]) 4 res = [] 5 for i in nums: 6 if not res or res[-1][1] < i[0]: 7 res.append(i) 8 else: 9 res[-1][1] = max(res[-1][1], i[1]) 10 return res 11 12 def insert_Matrix(nums, target): 13 for i in range(len(nums)): 14 if target[0] < nums[i][0]: 15 nums.insert(i, target) 16 break 17 return merge(nums) 18 19 print(insert_Matrix([[1, 3], [6, 9]], [2, 5]))
1 #贪心 2 def insertGreedy(nums, target): 3 res = [] 4 i = 0 5 while i < len(nums): 6 if nums[i][0] < target[0]: 7 res.append(nums[i]) 8 i += 1 9 else: 10 break 11 if not res or res[-1][1] < target[0]: 12 res.append(target) 13 else: 14 res[-1][1] = max(res[-1][1], target[1]) 15 while i < len(nums): 16 if res[-1][1] < nums[i][0]: 17 res.append(nums[i]) 18 else: 19 res[-1][1] = max(res[-1][1], nums[i][1]) 20 i += 1 21 return res 22 23 print(insertGreedy([[1, 3], [6, 9]], [2, 5]))