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 。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
# 题目要求时间复杂度为logn,因此基本就是二分法了。 这道题目不是直接的有序数组,不然就是easy了。
# 首先要知道,我们随便选择一个点,将数组分为前后两部分,其中一部分一定是有序的。
#
# 具体步骤:
# 我们可以先找出mid,然后根据mid来判断,mid是在有序的部分还是无序的部分
# 假如mid小于start,则mid一定在右边有序部分。
# 假如mid大于等于start, 则mid一定在左边有序部分。
# 我们只需要比较target和有序部分的边界关系就行了。 比如mid在右侧有序部分,即[mid, end]
# 那么我们只需要判断 target >= mid && target <= end 就能知道target在右侧有序部分,我们就
# 可以舍弃左边部分了(start = mid + 1), 反之亦然。
class Solution:
def search(self, nums: List[int], target: int) -> int:
n = len(nums)
if n <= 0:
return -1
left = 0
right = n - 1
while left < right:
mid = (right - left) // 2 + left
if nums[mid] == target:
return mid
# 找到顺序的序列范围
if nums[mid] > nums[left]: # 如果中间的值大于最左边的值,说明左边有序
if nums[left] <= target <= nums[mid]:
right = mid
else:
# 这里 +1,因为上面是 <= 符号
left = mid + 1
# 否则右边有序
else:
# 注意:这里必须是 mid+1,因为根据我们的比较方式,mid属于左边的序列
if nums[mid + 1] <= target <= nums[right]:
left = mid + 1
else:
right = mid
return left if nums[left] == target else -1
81
已知存在一个按非降序排列的整数数组 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,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。
给你 旋转后 的数组 nums 和一个整数 target ,
请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。
示例1:
输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
示例2:
输入:nums = [2,5,6,0,0,1,2], target = 3
输出:false
# 二分查找
# 对于数组中有重复元素的情况,二分查找时可能会有 a[l]=a[mid]=a[r],此时无法判断区间 [l,mid] 和区间 [mid+1,r] 哪个是有序的。
# 对于这种情况,我们只能将当前二分区间的左边界加一,右边界减一,然后在新区间上继续二分查找
class Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums:
return False
n = len(nums)
if n == 1:
return nums[0] == target
l, r = 0, n - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return True
if nums[l] == nums[mid] and nums[mid] == nums[r]:
l += 1
r -= 1
elif nums[l] <= nums[mid]:
if nums[l] <= target and target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else:
if nums[mid] < target and target <= nums[n - 1]:
l = mid + 1
else:
r = mid - 1
return False
153
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组[0,1,2,4,5,6,7] 可能变为[4,5,6,7,0,1,2])。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
# 二分
class Solution:
def findMin(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] > nums[right]: # 说明 nums[mid] 是最小值左侧的元素, 因此我们可以忽略二分查找区间的左半部分
left = mid + 1
elif nums[mid] < nums[right]: # 说明 nums[mid] 是最小值右侧的元素, 因此我们可以忽略二分查找区间的右半部分
right = mid
return nums[left]
154
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组[0,1,2,4,5,6,7] 可能变为[4,5,6,7,0,1,2])。
请找出其中最小的元素。
注意数组中可能存在重复的元素。
示例 1:
输入: [1,3,5]
输出: 1
示例2:
输入: [2,2,2,0,1]
输出: 0
def findMin(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] > nums[right]:
left = mid + 1
elif nums[mid] < nums[right]:
right = mid
else: # 出现相等的情况
# 由于它们的值相同,所以无论 nums[high] 是不是最小值,都有一个它的「替代品」nums[pivot],因此我们可以忽略二分查找区间的右端点
right -= 1
return nums[left]
1723
给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。
请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。
工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。
返回分配方案中尽可能 最小 的 最大工作时间 。
示例 1:
输入:jobs = [3,2,3], k = 3
输出:3
解释:给每位工人分配一项工作,最大工作时间是 3 。
示例 2:
输入:jobs = [1,2,4,7,8], k = 2
输出:11
解释:按下述方式分配工作:
1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)
2 号工人:4、7(工作时间 = 4 + 7 = 11)
最大工作时间是 11 。
# 二分 + 递归 + 剪枝
# 参考 1011
# 我们想象一下 K 个人,可以设置一个每个人最大运输量大小为limit,如果在这个limit下,
# 工作能分完,这个方案是可行的,如果在这个limit 下分不完,那这个方案不可行。
class Solution:
def minimumTimeRequired(self, jobs: List[int], k: int) -> int:
def check(limit): # 检查这个limit行不行
arr = sorted(jobs) # 小优化,排序后,大的先拿出来试
groups = [0] * k
# 分成 K 组,看看在这个limit 下 能不能安排完工作
if backtrace(arr, groups, limit):
return True
else:
return False
def backtrace(arr, groups, limit):
# 尝试每种可能性
if not arr:
return True # 分完,则方案可行
v = arr.pop() # 取出最大的工作
for i in range(len(groups)):
if groups[i] + v <= limit:
groups[i] += v
if backtrace(arr, groups, limit): # 递归
return True
groups[i] -= v # 上一步不是true话, 回溯, 接着递归
# 剪枝,如果这个工人没分到活,那别人肯定得多干活了,那最后的结果必然不是最小的最大值,就不用继续试了。
if groups[i] == 0:
break
arr.append(v) # 可以不加, 但上面递归行要改成 if backtrace(arr.copy(), groups, limit)
return False
# 每个人承担的工作的上限,最小为 job 里面的最大值,最大为 jobs 之和
left, right = max(jobs), sum(jobs)
while left < right: # 开始二分查找
mid = (left + right) // 2
if check(mid): # 可以完成工作
right = mid
else: # 不能完成
left = mid + 1
return left
1482
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,每束需要使用花园中 相邻的 k 朵花 。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例 1:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3
示例 2:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。
示例 3:
输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。
示例 4:
输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束
示例 5:
输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9
# 二分
class Solution:
def minDays(self, bloomDay: List[int], m: int, k: int) -> int:
# 最好用除法, 因为10 ^ 5 * 10 ^ 6 = 10 ^ 11 > 2, 147, 483, 647, 会越界
if m > len(bloomDay) / k: # 花的数量不够
return -1
def canMake(target_days: int) -> bool:
bouquets = flowers = 0 # 分别为 目前制作的花束数量 和 连续花朵数量
for day in bloomDay:
if day <= target_days: # 此花能及时开
flowers += 1
if flowers == k: # 制作1束花
bouquets += 1
if bouquets == m: # 满足题目要求
return True
flowers = 0
else: # 每束花的每朵花必须相连, 如中断则重新计数
flowers = 0
return bouquets == m
left, right = min(bloomDay), max(bloomDay) # 左右指针
while left < right: # 开始二分查找
target_days = (left + right) // 2 # 找到最小的天数
if canMake(target_days): # 此函数检查天数够不够
right = target_days
else:
left = target_days + 1
return left
276
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用bool isBadVersion(version)接口来判断版本号 version 是否在单元测试中出错。
实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5)-> true
调用 isBadVersion(4)-> true
所以,4 是第一个错误的版本。
# 二分
# 将左右边界分别初始化为 1 和 n,其中 n 是给定的版本数量。
# 设定左右边界之后,每次我们都依据左右边界找到其中间的版本,检查其是否为正确版本。
# 如果该版本为正确版本,那么第一个错误的版本必然位于该版本的右侧,我们缩紧左边界;
# 否则第一个错误的版本必然位于该版本及该版本的左侧,我们缩紧右边界。
# 这样我们每判断一次都可以缩紧一次边界,而每次缩紧时两边界距离将变为原来的一半,因此我们至多只需要缩紧 O(\log n)O(logn) 次。
class Solution:
def firstBadVersion(self, n):
left, right = 1, n
while left < right:
mid = (left + right) // 2
if isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
注意:
应该避免使用 mid = (left + right) // 2;
因为这样有些时候会出现数字过大的溢出情况。
所以, 应该使用 mid = left + (right - left) / 2;
852
符合下列属性的数组 arr 称为 山脉数组 :
arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < … arr[i-1] < arr[i]
arr[i] > arr[i+1] > … > arr[arr.length - 1]
给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i 。
示例 1:
输入:arr = [0,1,0]
输出:1
示例 2:
输入:arr = [0,2,1,0]
输出:1
示例 3:
输入:arr = [0,10,5,2]
输出:1
# 暴力
class Solution:
def peakIndexInMountainArray(self, arr: List[int]) -> int:
for i in range(1, len(arr) - 1):
if arr[i] > arr[i + 1]:
return i
# 二分
class Solution:
def peakIndexInMountainArray(self, arr: List[int]) -> int:
n = len(arr)
left, right, res = 1, n - 1, 0
while left < right:
mid = (left + right) // 2
if arr[mid] > arr[mid + 1]: # [mid, right] 为上升区间
res = mid
right = mid
else: # [left, mid] 为上升区间
left = mid + 1
return res
528
给定一个正整数数组w ,其中w[i]代表下标 i的权重(下标从 0 开始),请写一个函数pickIndex,它可以随机地获取下标 i,选取下标 i的概率与w[i]成正比。
例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3)= 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3)= 0.75(即,75%)。
也就是说,选取下标 i 的概率为 w[i] / sum(w) 。
示例 1:
输入:
[“Solution”,“pickIndex”]
[[[1]],[]]
输出:
[null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
示例 2:
输入:
[“Solution”,“pickIndex”,“pickIndex”,“pickIndex”,“pickIndex”,“pickIndex”]
[[[1,3]],[],[],[],[],[]]
输出:
[null,1,1,1,1,0]
解释:
Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。
由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
…
诸若此类。
class Solution:
def __init__(self, w: List[int]):
self.pre = list(itertools.accumulate(w)) # 进行前缀和计算
self.total = self.pre[-1] # 和
def pickIndex(self) -> int:
x = random.randint(1, self.total)
return bisect_left(self.pre, x) # 返回索引