算法学习——二分查找

1. 二分查找


系列文章目录

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(logn),数组有n个元素,假设n是2的幂,第一次比较后剩下n/2个元素,第二次比较后剩下(n/2)/2个元素,以此类推,k次比较之后,剩下n/2^k个元素。当k=log_{2}n时候,只剩下一个元素,最多再比1次。因此,二分查找法最糟情况下需要比较\log_{2}n +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

二、算法笔试题

1.缺失数字(*)

代码如下(示例):

'''
缺失数字
从 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

2.求平方根(**)

代码如下(示例):

'''
求平方根
实现函数 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

3.在旋转过的有序数组中寻找目标值(**)

代码如下(示例):

'''
在旋转过的有序数组中寻找目标值
给定一个整数数组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

4.旋转数组的最小数字(**)

代码如下(示例):

’‘’
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
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

5.数字在升序数据中出现的次数(**)

代码如下(示例):

'''
数字在升序数据中出现的次数
统计一个数字在升序数组中出现的次数。
'''
# 输入:[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

6.最长递增子序列(***)(贪心+二分)

代码如下(示例):

’‘’
给定数组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

7.二分查找(***)

代码如下(示例):

’‘’
请实现有重复数字的升序数组的二分查找
给定一个 元素有序的(升序)整型数组 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

8.在两个长度相等的排序数组中找到上中位数(****)

代码如下(示例):

’‘’
给定两个有序数组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是否需要

你可能感兴趣的:(算法,二分法,算法,数组,python)