有/无重复二分查找,矩阵查找,有序数组中位数,接雨水算法

Posted Pistachiout

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有/无重复二分查找,矩阵查找,有序数组中位数,接雨水算法相关的知识,希望对你有一定的参考价值。

1、二分查找问题:

一个无重复的有序整数数组中查找某个数的位置,如果找到则返回下标,否则返回-1。
在这里插入图片描述

def BinarySearch(Arr,x):#二分查找
    if len(Arr)==0:
       return -1
    left=0
    right=len(Arr)-1
    while right>=left:
        mid=(int)((left+right)/2)
        if Arr[mid]<x:
            left=mid+1
        elif Arr[mid]>x:
            right=mid-1
        else: 
            return mid
    return -1
Array=[0,1,2,3,5,6,7,8,10,55]
print(Array)
print(BinarySearch(Array,5))

二分查找算法,每次循环都相当于去掉一半的值,并将剩下一半的值内设好right,left,mid等索引,然后在剩下一半的值内查找,直到找到值,由此可以很快的找到所要找的值.

2、 有重复的二分查找

:在一个可重复的升序的整数数组中查找某个数的开始位置和结束位置。 如果数组中不存在,则返回 [-1, -1]。 算法时间复杂度要求为 O(log n) 。
示例1:
输入:nums = [5,7,7,8,8,10]
target = 8
输出:[3,4]
示例2:
输入:nums = [5,7,7,8,8,10]
target = 6
输出:[-1,-1]
在这里插入图片描述

  • 在看到这道题时,我马上想到还是使用二分法查找到所要找的target的其中一个,然后再依次查找所找到target的索引两边的值,直到索引左边或右边的值不再是所查值target时,但很快便发现这样的时间复杂度不是O(log n),于是只好改变算法。
    因此想到用二分查找法找到其中一个Arr[mid]=target后,不查找到target的所有范围,而只是对比target左边或右边一个位置的值,如果Arr[mid]左边还有target,则将right等于mid-1,循环将在已找到的target左边继续查找,直到Arr[mid]左边不是target,这样返回的mid则是target在数组中在开始的索引;如果Arr[mid]右边还有target,则将left等于mid+1,循环将在已找到的target右边继续查找,直到Arr[mid]右边不是target,这样返回的mid则是target在数组中最后的索引,而且由于每次循环仅判断target左边或右边一次的值,故数据循环度还是O(log n).这样将找到开始值与结束值分别用函数表示出来,从而得到target的范围,这样完成了有重复的某个数的开始位置和结束位置的查找,且时间复杂度符合要求。
def searchFirstIndex(Arr,target):#定义函数得到查找数的开始位置
    if len(Arr)==0:#如果数组不存在,返回-1
        return -1
    left=0
    right=len(Arr)-1#设置左、右两个坐标
    while right>=left:#循环条件为右坐标大于左坐标
        mid=left+(right-left)//2#每次循环重新取mid为left与right的中值
        if Arr[mid]>target:#如果mid的值小于target的值,则x位于mid右边,left+1
            right=mid-1
        elif Arr[mid]<target:#如果mid的值大于target的值,则x位于mid左边,right-1
            left=mid+1
        else:
            if mid==left or Arr[mid-1]!=target:#如果mid已是所查数的开始位置
                return mid
            right=mid-1
			#mid左边还有target,则将right等于mid-1,循环将在已找到的target左边继续查找,直到mid左边不是target
    return -1
def searchLastIndex(Arr,target):#定义函数得到查找数的结束位置
    if len(Arr)==0:
        return -1
    left=0
    right=len(Arr)-1
    while right>=left:
        mid=left+(right-left)//2
        if Arr[mid]>target:
            right=mid-1
        elif Arr[mid]<target:
            left=mid+1
        else:
            if mid==left or Arr[mid-1]!=target:#如果mid已是所查数的最后位置
                return mid
            left=mid+1
			#mid右边还有target,则将left等于mid+1,循环将在已找到的target右边继续查找,直到mid右边不是target
    return -1
def searchRange(Arr,target):#定义函数得到查找数的范围
    first=searchFirstIndex(Arr,target)
    last=searchLastIndex(Arr,target)
    return [first,last]
Arr=[1,2,3,5,5,5,5,6,7,8,9,10]
print (searchRange(Arr,5))

3、矩阵查找问题:

在一个 m x n 的有序整数矩阵中查找某个数。有序矩阵是指每行中的整数从左到右为升序排列、每行的第一个整数大于前一行的最后一个整数。找到返回true,否则返回false。
示例1:
输入:matrix = [ [1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]]
target = 3
输出:true
示例2:
输入:matrix = [ [1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]]
target = 13
输出:false
在这里插入图片描述

首先想到将矩阵转换为数组,对数组进行二分查找找到所查的数,而且也很好实现。
在这里插入图片描述

之后又想到一种方法,由于有序矩阵是指每行中的整数从左到右为升序排列、每行的第一个整数大于前一行的最后一个整数,因此我们可以先对每一行最后一列的值与所查的值比较,这样就能找到所查值所在的行,然后再在该行进行遍历,便能找到所查的值是否存在。存在则返回true,否则返回false。

def searchMatrix(matrix,num):
    i = 0
    j = len(matrix[0]) - 1#j为列值
    max_i = len(matrix) - 1#行值
    while i <= max_i and j >= 0:#循环条件
        if matrix[i][j] == num:
            return True#存在返回true
        elif matrix[i][j] <num:#当值比所求值小,则增加行数,得到所求值的行数
            i = i + 1
        else:#在行内减少列数,得到是否存在所求值
            j = j - 1
    return False#不存在返回false
matrix=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]
print(searchMatrix(matrix,5))

4、 寻找两个有序数组的中位数

:给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 找出这两个有序数组的中位数, 假设 nums1 和 nums2 不会同时为空,要求算法的时间复杂度为 O(log(m + n))。
示例1:输入:nums1 = [1, 3] nums2 = [2] 输出:2.0
示例2:输入:nums1 = [1, 2] nums2 = [3, 4] 输出:2.5
在这里插入图片描述

  • 看到2个有序数组,由于学习过归并排序的缘故马上想到将两个数组归并到一个数组后使用得到中位数,但要求算法的时间复杂度为 O(log(m + n)),很明显归并法并不满足。
    在进过星期三递归算法的学习后,知道递归算法的时间复杂度是log类型的,于是考虑使用递归法求解,但还是没有想到算法。于是百度后发现有很多不同的解析,有些解析写得很深奥,我认真学习了其中最容易懂的算法。
    该方法的关键函数就是找到2个有序数组中第k大值(从1开始)的函数,该函数就是运用了递归法,先将所求2个数组的num【k//2-1】进行比较看,哪个数组的num【k//2-1】小,而两个k//2的数最多k个数,则第k大的数必不在num【k//2-1】小的前k//2个数内,故递归,对该数组从num【k//2+1】与另一数组再次使用函数,并使k=k-k//2,一直循环,直到k=1时返回2个数组中最小的值,这就是2个有序数组中第k大的值。如[1,2,3,4]与[5,6,7]两个数组,对比nums【4//2-1】,明显知道[1,2,3,4]内2比[5,6,7]中6小,因此2必是比第k个数小的数,因此去掉1,2,递归继续在[3,4]与[5,6,7]内找到第4-2=2大的数。
    若两数组总长度length为奇数,查找中位数即找到两个数组第length//2大的数,两数组总长度length为奇数length为偶数,查找中位数即找到两个数组第length//2与第length//2+1个数的平均值。
def findkth(nums1,nums2,k):#递归找到两个数组中第k大的数
    if len(nums1)>len(nums2):#保证nums1的长度比nums2的长度短
        return findkth(nums2,nums1,k)
    if(len(nums1)==0):#如果nums1为空,则返回nums2的第k大的数
        return nums2[k-1]
    if k==1:#k=1时,返回最小的值
        return min(nums1[0],nums2[0])
    i=k//2
    if nums1[i-1]>nums2[i-1]:
    #比较数组的第i个数,如果nums1[i-1]大,而两个i的数组内最多k个数,
    #则第k大的数必不在nums2的前k//2个数内,故返回num1与nums2[i:]的第k-1个小的数
        return findkth(nums1,nums2[i:],k-i)
    else:
        return findkth(nums1[i:],nums2,k-i)
    
def findmid(nums1,nums2):
    length=len(nums1)+len(nums2)
    if length%2==1:#length为奇数,查找中位数即找到两个数组第length//2大的数
        return findkth(nums1,nums2,length//2+1)
    elif length%2==0:#length为偶数,查找中位数即找到两个数组第length//2与第length//2+1个数的平均值
        return (findkth(nums1,nums2,length//2)+findkth(nums1,nums2,length//2+1))/2.0

nums1=[1,2,3,4,5]
nums2=[5,6,7,8,11]
print(findmid(nums1,nums2))

5、 假设给定的一个数组描述了 n 个宽度为 1 的柱子高度,请求出按此排列的柱子,下雨之后能接多少雨水。

示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
示例图:
在这里插入图片描述

  • 刚看到该题时,以为改题目很难,但认真想了一会便发现很简单,将接到水按每一高度来算,在这一高度想要接到水,至少这一高度要有2个该高度的柱子,且接到水的量等于两个柱子索引的差再减一,即类似种树问题。因此,从数组最大值到1进行遍历,每次遍历找到数组中等于该值的所有索引,再将该高度的水全加起来,如高度=2时,数组值等于2的下标为[3, 7, 8, 10],3到7有3格水,7到8接不到水,8到10一格水,则2这一高度共有4格水。因此将高度从最大值到1每一高度的水加起来就能得到接到水的总量
def sumOfWater(nums):
    num=max(nums)#num为遍历的值,从最大值开始遍历
    sum=0#sum表示接到水的重量
    for num in range (max(nums),0,-1):#对数组按值从最大值到1遍历,每次循环sum增加所遍历高度能接到的水
        temp=[]#数组temp用来记录大于等于遍历值的数组索引
        for j in range (0,len(nums)):#下标遍历数组,temp记录索引
            if nums[j]>=num:
                temp.append(j)
        print(temp)#输出数组便于观察
        if len(temp)>1:#当数组中遍历的值的个数大于1for index in range (0,len(temp)-1):#分别求出该遍历高度每2个索引间的水数
                sum=sum+temp[index+1]-temp[index]-1
				#如num=2时,temp为[3, 7, 8, 10]373格水,78没水,810一格水
    return sum
nums= [0,1,0,2,1,0,1,3,2,1,2,1]
print(sumOfWater(nums))

以上是关于有/无重复二分查找,矩阵查找,有序数组中位数,接雨水算法的主要内容,如果未能解决你的问题,请参考以下文章

[LeetCode]Median of Two Sorted Arrays 二分查找两个有序数组的第k数(中位数)

漫画:“旋转数组”中的二分查找

二分查找(允许重复元素的有序数组)

查找算法之“二分查找”

二分查找法(折半查找法)

[算法必须死 第3期] 旋转数组的二分查找