leetcode - 二分查找

Posted 一杯敬朝阳一杯敬月光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode - 二分查找相关的知识,希望对你有一定的参考价值。

目录

69. x 的平方根 

268. 丢失的数字

278. 第一个错误的版本

441. 排列硬币

704. 二分查找

744. 寻找比目标字母大的最小字母

1539. 第 k 个缺失的正整数 

888. 公平的糖果交换

2089. 找出数组排序后的目标下标 

33. 搜索旋转排序数组 

153. 寻找旋转排序数组中的最小值

162. 寻找峰值 


69. x 的平方根 

力扣给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

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

    def mySqrtV1(self, x):
        """
        :type x: int
        :rtype: int
        """
        l, r = -1, x 
        while l < r:
            # (l, r]
            mid = l + (r - l + 1) // 2
            if mid * mid <= x:
                l = mid 
            else:
                r = mid - 1
        return l

268. 丢失的数字

力扣给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。 

位运算

class Solution(object):
    def missingNumber(self, nums):
        ans = 0
        ans ^= len(nums)
        for i, num in enumerate(nums):
            ans ^= i 
            ans ^= num 
        return ans

 二分查找

class Solution(object):
    def missingNumber(self, nums):
        nums = sorted(nums)
        l, r = 0, len(nums)
        # [l, r), 第一个nums[mid] > mid的mid就是缺失的数
        while l < r:
            mid = l + (r - l) // 2
            if nums[mid] <= mid:
                l = mid + 1
            else:
                r = mid 
        return r

278. 第一个错误的版本

力扣你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

class Solution(object):
    def firstBadVersion(self, n):
        # 第一个True
        l, r = 0, n + 1
        while l < r:
            # [l, r)
            mid = l + (r - l) // 2
            if not isBadVersion(mid):
                l = mid + 1 
            else:
                r = mid 
        return r

441. 排列硬币

力扣你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。

给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。

​​

class Solution(object):
    def arrangeCoins(self, n):
        # 1 + 2 + 3 + ... + k = (k + 1) * k // 2
        l, r = 0, n
        # 小于等于的最大值
        while l < r:
            mid = l + (r - l + 1) // 2
            if mid * (mid + 1) // 2 <= n:
                l = mid 
            else:
                r = mid - 1
        return l

704. 二分查找

力扣给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

class Solution(object):
    def search(self, nums, target):
        l, r = 0, len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid 
            elif nums[mid] < target:
                l = mid + 1
            else: 
                r = mid - 1
        return -1

744. 寻找比目标字母大的最小字母

力扣给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。

在比较时,字母是依序循环出现的。举个例子:

如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a'

class Solution(object):
    def nextGreatestLetter(self, letters, target):
        l, r = 0, len(letters)
        while l < r:
            mid = l + (r - l) // 2
            if letters[mid] <= target:
                l = mid + 1 
            else:
                r = mid 
        if r == len(letters):
            return letters[0]
        return letters[r]

1539. 第 k 个缺失的正整数 

 力扣给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。请你找到这个数组里第 k 个缺失的正整数。

示例 1:

输入:arr = [2,3,4,7,11], k = 5,输出:9
解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,...] 。第 5 个缺失的正整数为 9 。
示例 2:

输入:arr = [1,2,3,4], k = 2,输出:6
解释:缺失的正整数包括 [5,6,7,...] 。第 2 个缺失的正整数为 6 。

力扣

class Solution(object):
    def findKthPositive(self, arr, k):
        n = len(arr)
        if arr[0] - 1 >= k:
            return k 

        l, r = -1, n - 1
        while l < r:
            mid = l + (r - l + 1) // 2
            if arr[mid] - (mid + 1) >= k:
                r = mid - 1
            else:
                # 一定进得来,因为之前处理过边界,所以最终l一定大于等于0
                l = mid
        return k - (arr[l] - (l + 1)) + arr[l]

888. 公平的糖果交换

力扣爱丽丝和鲍勃拥有不同总数量的糖果。给你两个数组 aliceSizes 和 bobSizes ,aliceSizes[i] 是爱丽丝拥有的第 i 盒糖果中的糖果数量,bobSizes[j] 是鲍勃拥有的第 j 盒糖果中的糖果数量。

两人想要互相交换一盒糖果,这样在交换之后,他们就可以拥有相同总数量的糖果。一个人拥有的糖果总数量是他们每盒糖果数量的总和。

返回一个整数数组 answer,其中 answer[0] 是爱丽丝必须交换的糖果盒中的糖果的数目,answer[1] 是鲍勃必须交换的糖果盒中的糖果的数目。如果存在多个答案,你可以返回其中 任何一个 。题目测试用例保证存在与输入对应的答案。

class Solution(object):
    def fairCandySwap(self, aliceSizes, bobSizes):
        s1, s2 = sum(aliceSizes), sum(bobSizes)
        bobSizes = sorted(bobSizes)

        for one in aliceSizes:
            target = one + (s2 - s1) // 2
            l, r = 0, len(bobSizes) - 1
            while l <= r:
                mid = l + (r - l) // 2
                if bobSizes[mid] == target:
                    return [one, target]
                elif bobSizes[mid] < target:
                    l = mid + 1
                else:
                    r = mid - 1
        return [0, 0]

2089. 找出数组排序后的目标下标 

力扣给你一个下标从 0 开始的整数数组 nums 以及一个目标元素 target 。

目标下标 是一个满足 nums[i] == target 的下标 i 。

将 nums 按 非递减 顺序排序后,返回由 nums 中目标下标组成的列表。如果不存在目标下标,返回一个 空 列表。返回的列表必须按 递增 顺序排列。

class Solution(object):
    def targetIndices(self, nums, target):
        nums = sorted(nums)
        if target < nums[0] or target > nums[-1]:
            return [] 
        
        # ceil
        def ceil(nums, target):
            l, r = 0, len(nums)
            while l < r:
                # [l, r)
                mid = l + (r - l) // 2
                if nums[mid] <= target:
                    l = mid + 1
                else:
                    r = mid
            if r - 1 >= 0 and nums[r - 1] == target:
                return r - 1
            return -1

        def floor(nums, target):
            l, r = -1, len(nums) - 1
            while l < r:
                # (l, r]
                mid = l + (r - l + 1) // 2
                if nums[mid] < target:
                    l = mid 
                else:
                    r = mid - 1
            if l + 1 < len(nums) and nums[l + 1] == target:
                return l + 1
            return -1

        idx_last = ceil(nums, target)
        if idx_last == -1:
            return []
        
        idx_first = floor(nums, target)

        return [i for i in range(idx_first, idx_last + 1)]

 还可以定义两个变量,一个统计小于target的值,一个统计等于target的值。然后就可以得出左右边界啦,时间复杂度只有O(1)。

33. 搜索旋转排序数组 

力扣整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0, 输出:4
示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3, 输出:-1
示例 3:

输入:nums = [1], target = 0, 输出:-1

class Solution(object):
    def search(self, nums, target):
        l, r = 0, len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid

            # 前面有序,注意这边有等号
            if nums[mid] >= nums[0]:
                if nums[l] <= target < nums[mid]:
                    r = mid - 1
                else:
                    l = mid + 1
            # 后面有序
            else:
                if nums[mid] < target <= nums[r]:
                    l = mid + 1
                else:
                    r = mid - 1
        return -1

153. 寻找旋转排序数组中的最小值

力扣已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2], 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7], 注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:输入:nums = [3,4,5,1,2], 输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:输入:nums = [4,5,6,7,0,1,2], 输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。
示例 3:输入:nums = [11,13,15,17], 输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

class Solution(object):
    def findMin(self, nums):
        l, r = 0, len(nums) - 1
        ans = nums[0]
        while l <= r:
            mid = l + (r - l) // 2
            # 前面有序
            if nums[mid] >= nums[0]:
                ans = min(ans, nums[0])
                l = mid + 1
            # 后面有序
            else:
                ans = min(ans, nums[mid])
                r = mid - 1
        return ans

162. 寻找峰值 

力扣峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。

提示:

       1 <= nums.length <= 1000
        -231 <= nums[i] <= 231 - 1
        对于所有有效的 i 都有 nums[i] != nums[i + 1]

 见官方题解:力扣 乍一看,和二分没关系,其实可以理解为每次选择抛弃掉一部分,在另一部分寻找最大值。最大值一定是峰值,因为相邻元素不想等,且nums[-1] = nums[n] = -∞ 。

class Solution(object):
    def findPeakElement(self, nums):
        def get_value(nums, i):
            if i == -1 or i == len(nums):
                return float("-inf")
            return nums[i]

        l, r = 0, len(nums) - 1
        while l <= r:
            mid = l + (r - l) // 2 
            if get_value(nums, mid - 1) < nums[mid] and get_value(nums, mid + 1) < nums[mid]:
                return mid 
            elif get_value(nums, mid - 1) > nums[mid]:
                r = mid - 1
            else:
                l = mid + 1
        return -1

以上是关于leetcode - 二分查找的主要内容,如果未能解决你的问题,请参考以下文章

leetcode(162)---寻找峰值(二分查找)

Leetcode No.162 寻找峰值(二分查找)

Leetcode No.162 寻找峰值(二分查找)

二分查找leetcode题目

再也不怕女朋友问我二分查找了!!!手绘漫画面试必考之二分查找(解题模板和深度剖析),最终回

LeetCode 162 寻找峰值[二分法] HERODING的LeetCode之路