二分查找训练

二分查找训练

题目一:2020.08.29 [003] 魔术索引

1.题目描述

在数组A[0...n-1]中,有所谓的魔术索引,满足条件 A[i] = i 。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。

示例1:
输入:nums = [0, 2, 3, 4, 5]
输出:0
说明: 0下标的元素为0

示例2:
输入:nums = [1, 1, 1]
输出:1

说明:
nums长度在[1, 1000000]之间
此题为原书中的 Follow-up,即数组中可能包含重复元素的版本

这道题出自于《程序员面试金典》 ,共有两个小问,第一小问为有序整数数组严格单调增,那么可以直接用二分法解决;第二小文与此题类似,整数数组单调不减,那么此题就不能继续用二分法做了。

2.解题思路

这道题有多种解法,最直接的就是遍历。

2.1 遍历查找

class Solution(object):
    def findMagicIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        for i in range(len(nums)):
            if(nums[i] == i):
                return i
        
        return -1

时间复杂度:O(n)
空间复杂度:O(1)

2.2 在遍历的基础上做一个跳跃的优化

class Solution(object):
    def findMagicIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        i = 0
        while i < len(nums):
            if(nums[i] == i):
                return i
            elif (i < nums[i]):
                i = nums[i]
            else:
                i += 1
        
        return -1

温馨提示:不可以使用 i ++ 哦
时间复杂度:O(n)
空间复杂度:O(1)

2.3 分治法

class Solution(object):
    def findMagicIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        def getAnswer(nums, left, right):
            if(left > right):
                return -1
            mid = (left + right) // 2
            ans = getAnswer(nums, left, mid - 1)
            if(ans != -1):
                return ans
            elif(nums[mid] == mid):
                return mid
            else: 
                return getAnswer(nums, mid + 1, right)
        return getAnswer(nums, 0, len(nums) - 1)

时间复杂度:O(n)
空间复杂度:O(1)

题目二:2020.08.30 [004] 搜索二维矩阵

1.题目描述

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

示例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

分类:此题若将二维矩阵看成一维的,那么就是一个典型的二分查找。

2.解题思路

由于二维矩阵是单调递增的,因此若将二维矩阵看做一维,就可以使用二分查找。
image.png
如上图所示,二分查找中用的索引是由右边的虚数组得到的,其中二维矩阵中索引和虚数组中索引的对应关系为:
row = idx // n
col = idx % n
python代码如下:

class Solution(object):
    def searchMatrix(self, matrix, target):
        m = len(matrix)
        if(m == 0):  #坑:矩阵可能为空
            return False  #python中bool变量是True和False(首字母大写哦)
        n = len(matrix[0])
        left, right = 0, m * n - 1
        while(left <= right):
            mid = (left + right) // 2
            num = matrix[mid // n][mid % n] #注意:数字编号和数组坐标的转换
            if(num == target):
                return True
            elif(num > target):
                right = mid - 1
            else:
                left = mid + 1
        return False

时间复杂度:O(log(mn))
空间复杂度:O(1)

小知识:

二维数组初始化的格式:
image.png
初始化一个 3 * 4 的矩阵:
image.png
错误的初始化方式:
image.png
由于这里使用了浅拷贝,因此生成了3个指向同一位置的引用,因此改变其中一行,其他行也跟着改变。

题目三:2020.08.31 [005] 0~n-1中的缺失数字

1.题目描述

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例1:
输入: [0,1,3]
输出: 2

示例2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8

限制:
1 <= 数组长度 <= 10000

tips:排序数组中的搜索问题,应当首先思考能否用二分法解决。

2.解题思路

2.1 遍历搜索

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        for i in range(len(nums)):
            if(i != nums[i]):
                return i
        return len(nums)

时间复杂度:O(n)
空间复杂度:O(1)

2.2 二分法

因此我们需要找到右子数组的首个元素,即第一个不等于索引的数字。
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        while(left <= right):
            mid = (left + right) // 2
            if(nums[mid] == mid):
                left = mid + 1
            else:
                right = mid - 1
        return left

对当前范围二分求 mid ,如果 mid 处的值等于 mid ,那就说明 mid 和它以前的位置上的值都等于它的索引,因此需要向后寻找第一个不等于索引的位置,那个位置就是解。因此将 left 赋值为 mid + 1;
如果 mid 处的值不等于 mid ,说明 mid 和后面的位置上的值都不等于索引,因此第一个不等于索引的位置要么就是 mid 要么就在 mid 之前。此时将 right 设置为 mid - 1 来向前寻找第一个值不等于索引的位置,如果 mid 是第一个不等于索引的位置的话,那么在向前搜索的过程中,一定会在 [left, mid - 1] 内找不到,直到最后 left = 此时的 mid。
因为结束是left总是大于right,表示第一个不等于索引的位置。
时间复杂度:O(n)
空间复杂度:O(1)

题目四:2020.09.01 [006] H指数

1.题目描述

给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列。编写一个方法,计算出研究者的 h 指数。

h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。(其余的N - h篇论文每篇被引用次数不多于 h 次。)"

示例:
输入: citations = [0,1,3,5,6]
输出: 3
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。

说明:
如果 h 有多有种可能的值 ,h 指数是其中最大的那个。

进阶:
这是H指数的延伸题目,本题中的citations数组是保证有序的。
你可以优化你的算法到对数时间复杂度吗?

2.解题思路

由于 H 指数的定义是大于等于 H 的引用数为 H 个,因此题目的解应当为满足大于等于 H 的数值刚好为 H 的数中最大的那个。也就是说,我们从后往前找的话,数组上第一个值 citations[i] >= 大于等于它的数的个数(也就是后面的值的个数+1 or n - i)时,此时我们就已经找到n - i了,因为此时正好有 n-i 个数大于等于 n - i .

2.1 遍历搜索

class Solution:
   def hIndex(self, citations: List[int]) -> int:
       n = len(citations)
       for i in range(n):
           if(citations[i] >= n-i):
               return n-i
       return 0

时间复杂度😮(n)
空间复杂度😮(1)

2.2二分查找

class Solution:
   def hIndex(self, citations: List[int]) -> int:
       n = len(citations)
       left, right = 0, n - 1
       while(left <= right):
           mid = (left + right) // 2
           if(citations[mid] >= n-mid):
               right = mid - 1
           else:
               left = mid + 1
       return n - left

说明:首先将搜索空间 [left=0, right=n-1] 进行二分:
如果 mid 位置上的值 >= n-mid,就说明右边存在 n-mid 个大于等于 n-mid 的引用数。此时已经出现了一个可行解了,但是还要继续向前找看看有没有 n-i 更大的值满足有 n-i 个大于等于 n-i 的引用值,因此将 right 的值设置为 mid - 1;
如果 mid 位置上的值 < n-mid,就说明后面的 n-mid 个数不全都大于等于 n-mid,因此要继续向右找到更小的 n-mid ,以及 mid 上面对应的更大的数值,因此将 left 设置为 mid+1。
当程序退出是 left > right,当最后一个满足 citations[mid] >= n-mid 之后(也就是最大的 h = n-mid),会将 right 设置为 mid-1 ,之后就不会改变了。最终 left 会指向此时 mid 的位置,因此结果为 n-left。
时间复杂度:O(logn)
空间复杂度:O(1)

题目五:2020.09.02 [007] x的平方根

1.题目描述

实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:
输入: 4
输出: 2

示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。

2.解题思路

求解 x 的平方根(取整),我们可以使用二分法对可能的范围 [left, right] 进行查找,直到找到第一个满足 mid × mid 小于等于 x 的数。
为了缩减范围,我们知道 x 的平方根小于等于(x+1)/2,(x+1)/2 <= x//2 + 1,因此我们将 right 初始化为(x//2) + 1。

class Solution:
    def mySqrt(self, x: int) -> int:
        l, r = 0, x//2 + 1
        while l <= r:
            mid = (l + r) // 2
            if mid * mid <= x:
                l = mid + 1
            else:
                r = mid - 1
        return r

题目六:2020.09.03 [008] 找出给定方程的正整数解

1.题目描述

给出一个函数 f(x, y) 和一个目标结果 z ,请你计算方程 f(x,y) == z 所有可能的正整数数对 x 和 y。
给定函数是严格单调的,也就是说:
f(x, y) < f(x + 1, y)
f(x, y) < f(x, y + 1)

函数接口定义如下:
interface CustomFunction {
public:
// Returns positive integer f(x, y) for any given positive integer x and y.
int f(int x, int y);
};
如果你想自定义测试,你可以输入整数 function_id 和一个目标结果 z 作为输入,其中 function_id 表示一个隐藏函数列表中的一个函数编号,题目只会告诉你列表中的 2 个函数。

你可以将满足条件的 结果数对 按任意顺序返回。

示例 1:
输入:function_id = 1, z = 5
输出:[[1,4],[2,3],[3,2],[4,1]]
解释:function_id = 1 表示 f(x, y) = x + y

示例 2:
输入:function_id = 2, z = 5
输出:[[1,5],[5,1]]
解释:function_id = 2 表示 f(x, y) = x * y

提示:
1 <= function_id <= 9
1 <= z <= 100
题目保证 f(x, y) == z 的解处于 1 <= x, y <= 1000 的范围内。
在 1 <= x, y <= 1000 的前提下,题目保证 f(x, y) 是一个 32 位有符号整数。

2.解题思路

利用单调性 f(x,y) < f(x,y+1) 来进行二分搜索。

class Solution:
    def findSolution(self, customfunction: 'CustomFunction', z: int) -> List[List[int]]:
        ans = []
        for i in range(1,z+1):
            left, right = 1, z
            while(left <= right):
                mid = (left + right) // 2           
                if(customfunction.f(i , mid) == z):
                    ans.append([i, mid])
                    break
                elif (customfunction.f(i, mid) < z):
                    left = mid + 1         
                else:
                    right = mid - 1
        return ans

时间复杂度:O(logz)
空间复杂度:O(1)

posted @ 2020-08-29 18:29  idella  阅读(297)  评论(0编辑  收藏  举报