leetcode - 二分查找
Posted 一杯敬朝阳一杯敬月光
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode - 二分查找相关的知识,希望对你有一定的参考价值。
目录
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 - 二分查找的主要内容,如果未能解决你的问题,请参考以下文章