牛客面试必刷TOP101——二分查找排序

列表

          • [二分查找-I BM17]
          • [二维数组中的查找 BM18]
          • [寻找峰值 BM19]
          • [组中的逆序对 BM20 ]
          • [旋转数组的最小数字 BM21]
          • [比较版本号 BM22]

[二分查找-I BM17]

原题

请实现无重复数字的升序数组的二分查找。给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1

class Solution:
    def search(self , nums: List[int], target: int) -> int:
        if not nums:
            return -1
        l = 0
        r = len(nums)-1
        while l <= r:
            m = l+(r-l)//2
            if target < nums[m]:
                r = m-1
            elif target > nums[m]:
                l = m+1
            else:
                return m
        return -1
[二维数组中的查找 BM18]

原题

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
给定 target = 7,返回 true。给定 target = 3,返回 false。

思路:左到右递增、上到下递增

从右上角元素开始,左边的元素都比其小,下面的元素都比其大,因此可以通过判断target和该元素的值确定左移还是下移。

class Solution:
    def Find(self , target: int, array: List[List[int]]) -> bool:
        # write code here
        if len(array) == 0:
            return False
        i = 0
        j = len(array[0])-1
        while i< len(array) and j >= 0:
            if target < array[i][j]:
                j -= 1
            elif target > array[i][j]:
                i += 1
            else:
                return True
        return False
[寻找峰值 BM19]

原题

给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。

1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于

2.假设 nums[-1] = nums[n] = −∞

3.对于所有有效的 i 都有 nums[i] != nums[i + 1]

4.你可以使用O(logN)的时间复杂度实现此问题吗?

思路1

题目保证没有相同元素,峰值是左右元素严格小于本身,所以最大值一定是峰值,因此只需要找到最大值的位置即可。

class Solution:
    def findPeakElement(self , nums: List[int]) -> int:
        # write code here
        return nums.index(max(nums))

思路2:分治法

假设数组两边是负无穷,则只要从两边往高处走,即可找到峰值。

  • 取中间元素mid,按照二分法可以将数组划分为【l , mid】和【mid+1 , r】
  • 比较nums[mid]和nums[mid+1]的值
  • 如果左边值大,说明右区间在下降,显然向右走不符合“往高处走",因此将区间往左缩小,即 r = m i d r=mid r=mid
  • 如果右边值大,说明向右走是在往高处走,因此将区间往右缩小, l = m i d + 1 l=mid+1 l=mid+1
  • l = = r l==r l==r时即到达峰值
class Solution:
    def findPeakElement(self , nums: List[int]) -> int:
        # write code here
        l = 0
        r = len(nums)-1
        while l < r:
            mid = (l+r) // 2
            if nums[mid] <= nums[mid+1]:
                l = mid + 1
            else:
                r = mid
        return l
[组中的逆序对 BM20 ]

原题

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007

思路

归并排序过程中计算逆序对,对于a和b,逆序对个数=a逆序对+b逆序对+合并ab的逆序对

  • 划分数组,直至子数组长度为1
  • 归并排序合并子数组,并计算逆序对
  • i指向相邻子数组左数组,j指向相邻子数组右数组;
  • 左到右遍历合并,对于 d a t a ( l , m i d , r ) data(l,mid,r) data(l,mid,r),令i = l, j = mid+1,依次将最小的元素填入合并的排序数组中。此处需要辅助数组temp存储原始两个待合并数组元素, d a t a ( l : r + 1 ) data(l:r+1) data(l:r+1)用于存储合并后数组元素。k的范围为(l,r),如果 t e m p [ i ] < = t e m p [ j ] temp[i]<=temp[j] temp[i]<=temp[j],则将 t e m p [ i ] temp[i] temp[i]填入data[k]中,如果 t e m p [ i ] > t e m p [ j ] temp[i]>temp[j] temp[i]>temp[j]大,说明存在逆序对,需要计算个数。考虑到temp(i : mid)本身就是有序的,所以如果temp[i]大于temp[j],那么 t e m p ( i + 1 , m i d ) temp(i+1,mid) temp(i+1,mid)都大于 t e m p [ j ] temp[j] temp[j],所以对于 t e m p [ j ] temp[j] temp[j]而言,前面可以有 m i d − i + 1 mid-i+1 midi+1个元素和其构成逆序对。因此当出现 t e m p [ i ] > t e m p [ j ] temp[i] > temp[j] temp[i]>temp[j]时,逆序对增加 m i d − i + 1 mid-i+1 midi+1个。
  • 还有一种方式是从右到左遍历,即每次将最大的元素填入合并的排序数组中,过程和左到右遍历类似。
class Solution:
    def InversePairs(self , data: List[int]) -> int:
        # write code here
        def divide(data,l,r,temp):
            if l >= r:
                return 0
            mid = (l+r)//2
            r1 = divide(data,l,mid,temp)
            r2 = divide(data,mid+1,r,temp)
            res = (r1 + r2) % 1000000007
            i,j = l,mid+1
            for k in range(l,r+1):
                temp[k] = data[k]
            for k in range(l,r+1):
                if i == mid+1:
                    data[k] = temp[j]
                    j += 1
                elif j == r+1 or temp[i] <= temp[j]:
                    data[k] = temp[i]
                    i += 1
                else:
                    res += mid - i + 1
                    data[k] = temp[j]
                    j += 1
            return res % 1000000007
             
        temp = [0 for _ in range(len(data))]
        return divide(data, 0, len(data)-1,temp)

#从右到左遍历
class Solution:
    def InversePairs(self , data: List[int]) -> int:
        # write code here
        def divide(data,l,r,temp):
            if l >= r:
                return 0
            mid = (l+r)//2
            r1 = divide(data,l,mid,temp)
            r2 = divide(data,mid+1,r,temp)
            res = (r1 + r2) % 1000000007
            i,j = mid,r
            for k in range(l,r+1):
                temp[k] = data[k]
            for k in range(r,l-1,-1):
                if j == mid:
                    data[k] = temp[i]
                    i -= 1
                elif i == l-1 or temp[i] <= temp[j]:
                    data[k] = temp[j]
                    j -= 1
                else:
                    res += j - mid
                    data[k] = temp[i]
                    i -= 1
            return res % 1000000007
             
        temp = [0 for _ in range(len(data))]
        return divide(data, 0, len(data)-1,temp)
[旋转数组的最小数字 BM21]

原题

有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

思路1

暴力法,原始非递减,旋转一部分元素放在数组后面,得到的旋转数组其实是两段非递减元素,因此只需要找到转折点,即后一个元素小于前面元素的位置,即为原来数组的第一个元素,也即最小元素。如果没有这个转折点,说明旋转数组的第一个元素就是最小的元素。

class Solution:
    def minNumberInRotateArray(self , rotateArray: List[int]) -> int:
        # write code here
        for i in range(1,len(rotateArray)):
            if rotateArray[i] < rotateArray[i-1]:
                return rotateArray[i]
        return rotateArray[0]

思路2

利用数组有序,可以使用二分法

  • 初始化左右边界l,r
  • 取中间值mid【对于数组[4,5,6,7,1,2],[4,5,6,7]为左排序数组,[1,2]为右排序数组】
  • 如果中间值小于最右边的值,说明中间值一定在右排序数组中;此时就缩小区间,r = mid
  • 如果中间值大于最右边的值,说明中间值一定在左排序区间中;此时缩小左区间,l = mid + 1
class Solution:
    def minNumberInRotateArray(self , rotateArray: List[int]) -> int:
        # write code here
        l,r = 0,len(rotateArray)-1
        while l < r:
            mid = (l+r) // 2
            if rotateArray[mid] < rotateArray[r]:
                r = mid
            elif rotateArray[mid] > rotateArray[r]:
                l = mid + 1
            else:
                r -= 1
        return rotateArray[l]
[比较版本号 BM22]

原题

牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等。现在给你2个版本号version1和version2,请你比较他们的大小。版本号是由修订号组成,修订号与修订号之间由一个"."连接。1个修订号可能有多位数字组成,修订号可能包含前导0,且是合法的。例如,1.02.11,0.1,0.2都是合法的版本号;每个版本号至少包含1个修订号。修订号从左到右编号,下标从0开始,最左边的修订号下标为0,下一个修订号下标为1,以此类推。

比较规则:
一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的
二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0",第3位修订号的下标为0,小于1
三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.

思路

使用**“.”**分割符将字符串划分,并将其转化为整数数组,就无需处理前导零,依次比较每个位置的元素
预处理:1、将两个数组长度对齐,短数组后部补0;2、记录最短长度遍历,遍历完再遍历另一个数组多出的元素,此处采用第一种预处理

class Solution:
    def compare(self , version1: str, version2: str) -> int:
        # write code here
        v1 = list(map(int,version1.split('.')))
        v2 = list(map(int,version2.split('.')))
        if len(v1) < len(v2):
            for j in range(len(v1),len(v2)):
                v1.append(0)
        elif len(v1) > len(v2):
            for j in range(len(v2),len(v1)):
                v2.append(0)
        for i in range(len(v1)):
            if v1[i] < v2[i]:
                return -1
            elif v1[i] > v2[i]:
                return 1
        return 0

你可能感兴趣的:(牛客,面试,leetcode,排序算法,二分查找,牛客)