系列文章目录
1. 二分查找
本篇目录
前言
一、算法介绍
二、算法笔试题
1.缺失数字(入门)
2.求平方根(简单)
3.在旋转过的有序数组中寻找目标值
4.旋转数组的最小数字
5.数字在升序数据中出现的次数
6.数字在升序数据中出现的次数
总结
文章主要用于自己学习,仅作为学习笔记分享。
算法介绍来自两本书《算法图解》、《Java语言程序设计与数据结构(基础篇)》
算法对应的笔试题来自牛客——https://www.nowcoder.com/activity/oj
二分查找的输入必须是一个有序的元素列表。
二分查找是在一个有序的数组中查找关键字target(设定target一开始的下界low=0,上界high=数组长度-1),需要将关键字与数组中间元素mid(mid=(low+high)//2向下取整)作比较:
1. target = mid,查找成功,返回mid下标
2. target > mid,目标在数组右半部分,low=mid+1
3. target < mid,目标在数组坐班部分,high=mid-1
二分查找运行时间为对数时间O(),数组有n个元素,假设n是2的幂,第一次比较后剩下n/2个元素,第二次比较后剩下(n/2)/2个元素,以此类推,k次比较之后,剩下个元素。当k=时候,只剩下一个元素,最多再比1次。因此,二分查找法最糟情况下需要比较次。
# 二分查找
def binary_search(list,item):
low = 0
high = len(list)-1
while low <= high: # low = high, 说明找到了唯一值
mid = (low + high) // 2 # 向下取整中间数
guess = list[mid]
if guess == item:
return mid
elif guess > item: # 猜大了,上界在左
high = mid - 1
else:
low = mid + 1 # 猜小了,下界在右
return none
my_list = [1,3,5,7,9]
print(binary_search(my_list,7))
# 3
代码如下(示例):
'''
缺失数字
从 0,1,2,...,n 这 n+1 个数中选择 n 个数,
选择出的数字依然保持有序,找出这 n 个数中缺失的那个数,
要求 O(n) 或 O(log(n)) 并尽可能小。
'''
# 输入:[0,1,2,3,4,5,7]
# 返回值:6
class Solution:
def solve(self , a ):
# write code here
low = 0
high = len(a) # 一共有n+1个数,所以一共有n个下标
while low <= high:
mid = (low + high) // 2
if a[mid] == mid: # 当下标=下标对应数组的值,则缺失值在右侧
low = mid + 1
if low == high: # 右侧仅有一个值的时候,缺的是mid+1
return mid+1
else:
high = mid # 当下标<下标对应数组的值,则缺失值在左侧
if low == high: # 当左侧仅有一个值的时候,缺的就是mid
return mid
return None
代码如下(示例):
'''
求平方根
实现函数 int sqrt(int x).
计算并返回x的平方根(向下取整)
'''
# 输入:2
# 返回值:1
# @param x int整型
# @return int整型
class Solution:
def sqrt(self , x ):
# write code here
if x <= 0:
return 0
low = 1
high = x
while low <= high:
mid = (low + high) // 2
if mid*mid <= x and (mid+1)*(mid+1) > x: # 当中间数平方小于x,且中间数右边平方大于x,则中间数就是平方根
return mid
elif mid*mid > x: # 中间数平方大于x,则向左边找
high = mid - 1
else:
low = mid + 1 # 中间数平方小于x,则向右边找
return None
代码如下(示例):
'''
在旋转过的有序数组中寻找目标值
给定一个整数数组nums,按升序排序,数组中的元素各不相同。
nums数组在传递给search函数之前,会在预先未知的某个下标 t(0 <= t <= nums.length-1)上进行旋转,让数组变为[nums[t], nums[t+1], ..., nums[nums.length-1], nums[0], nums[1], ..., nums[t-1]]。
比如,数组[0,2,4,6,8,10]在下标3处旋转之后变为[6,8,10,0,2,4]
现在给定一个旋转后的数组nums和一个整数target,请你查找这个数组是不是存在这个target,如果存在,那么返回它的下标,如果不存在,返回-1
'''
# 输入:[6,8,10,0,2,4],10
# 返回值:2
# @param nums int整型一维数组
# @param target int整型
# @return int整型
#
class Solution:
def search(self , nums , target ):
# write code here
low = 0
high = len(nums) - 1
while low < high:
mid = (low + high) // 2
if nums[mid] >= nums[low]: # low:mid有序
if target >= nums[low] and target <= nums[mid]:# target下标在low:mid
high = mid
else:# target下标在mid:high
low = mid + 1
else: # mid:high有序
if target >= nums[mid] and target <= nums[high]:# target下标在mid:high
low = mid
else:# target下标在low:mid
high = mid -1
if low == high and target == nums[low]:
return low
else:
return -1
代码如下(示例):
’‘’
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
‘’‘
# 输入:[3,4,5,1,2]
# 返回值:1
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
low = 0
high = len(rotateArray) - 1
if high == -1:
return 0
while low <= high:
mid = (low + high) // 2
# 中间值需要跟右端点比较,才能确定那个区域值偏大
if rotateArray[mid] > rotateArray[high]: # low:mid>min,最小值在mid右边
low = mid + 1
elif rotateArray[mid] == rotateArray[high]: # 最小值一定在high左边
high = high - 1
else: # mid:high>min,最小值在mid左边
high = mid
if low == high:
return rotateArray[low]
return None
代码如下(示例):
'''
数字在升序数据中出现的次数
统计一个数字在升序数组中出现的次数。
'''
# 输入:[1,2,3,3,3,3,4,5],3
# 返回值:4
class Solution:
def GetNumberOfK(self, data, k):
# write code here
lowL = lowR = 0
left = right = len(data) # 数字可能不存在
# 找下界
while lowL < left:
midL = (lowL + left) // 2
if data[midL] < k: # 下界在右边
lowL = midL + 1
else:
left = midL # 下界在左边,会出现lowL=left
# 找上界
while lowR < right:
midR = (lowR + right) // 2
if data[midR] <= k:
lowR = midR + 1 # 上界在右边,会出现lowR=right
else:
right = midR # 上界在左边
return lowR-left
代码如下(示例):
’‘’
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
‘’‘
# 输入:[1,2,8,6,4]
# 返回值:[1,2,4]
# 说明:
# 其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个 按数值进行比较的字典序 # 最小,故答案为(1,2,4)
#
# retrun the longest increasing subsequence
# @param arr int整型一维数组 the array
# @return int整型一维数组
#
class Solution:
def LIS(self , arr ):
# write code here
n = len(arr)
long = [0] * n
temp = []
for j in range(0,n):
# 二分查找arr[j]在子序列中的排列位置
low = 0
high = len(temp)
while low < high:
mid = (low + high) // 2
if temp[mid] >= arr[j]:
high = mid # target在左
else:
low = mid + 1 # target在右
if low == len(temp): # 如果有序子序列中最后一个元素小于arr[j],则在最后增加arr[j]
temp.append(arr[j])
else:
temp[low] = arr[j] # 如果有序子序列中从左到右第一个元素大于等于arr[j],则把该元素替换成arr[j]
long[j] = low # low代表arr[0]到arr[j]的最长子序列的长度
# 求字典序最小,倒着遍历arr每个元素对应的最长子序列的长度数组
l = len(temp)-1
for i in range(len(long)-1,-1,-1):
if long[i] == l:
temp[l] = arr[i]
l -= 1
return temp
代码如下(示例):
’‘’
请实现有重复数字的升序数组的二分查找
给定一个 元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的第一个出现的target,如果目标值存在返回下标,否则返回 -1
‘’‘
# 输入:[1,2,4,4,5],4
# 返回值:2
# 说明:
# 从左到右,查找到第1个为4的,下标为2,返回2
# 如果目标值存在返回下标,否则返回 -1
# @param nums int整型一维数组
# @param target int整型
# @return int整型
#
class Solution:
def search(self , nums , target ):
# write code here
low = 0
high = len(nums)
while low < high:
mid = (low + high) // 2
if nums[mid] >= target: # target在mid左
high = mid
else: # target在mid右
low = mid + 1
if len(nums) == 0 or nums[low] > target: # 判断target是否存在
return -1
else:
return low
return None
代码如下(示例):
’‘’
给定两个有序数组arr1和arr2,已知两个数组的长度都为N,求两个数组中所有数的上中位数。
上中位数:假设递增序列长度为n,若n为奇数,则上中位数为第n/2+1个数;否则为第n/2个数
[要求]
时间复杂度为O(logN)O(logN),额外空间复杂度为O(1)O(1)
‘’‘
# 输入:[1,2,3,4],[3,4,5,6]
# 返回值:3
# 说明:
# 总共有8个数,上中位数是第4小的数,所以返回3。
# find median in two sorted array
# @param arr1 int整型一维数组 the array1
# @param arr2 int整型一维数组 the array2
# @return int整型
#
class Solution:
def findMedianinTwoSortedAray(self , arr1 , arr2 ):
# write code here
low1 = low2 = 0
high1 = high2 = len(arr1) - 1
while low1 < high1:
mid1 = (low1 + high1) // 2
mid2 = (low2 + high2) // 2
length = high1 - low1 + 1
# 判断数组长度奇偶性,为了保证剩下的两个目标范围长度一致,防止漏掉mid是中位数的可能
if length % 2 == 0: # 有偶个元素,则目标范围内不包含mid
offset = 1
else:
offset = 0 # 有奇个元素,则目标范围内包含mid
if arr1[mid1] > arr2[mid2]: # 中位数在mid1左边,在mid2右边
high1 = mid1
low2 = mid2 + offset
elif arr1[mid1] < arr2[mid2]: # 中位数在mid1右边,在mid2左边
high2 = mid2
low1 = mid1 + offset
else:
return arr1[mid1] # 相等,即中位数就在mid1或mid2处
return min(arr1[low1],arr2[low2]) # 否则,中位数是最后算得的下界较小值
1. high的取值需要判断取数组长度还是长度-1,看数组是否存在缺失值;
2.目标范围在左或右,上下界是否需要包含mid,即low=mid+1/high=mid-1是否需要