LeetCode刷题1:第七周
目录
相关系列笔记:
LeetCode刷题:前言
LeetCode刷题1:第一周
LeetCode刷题1:第二周
LeetCode刷题1:第三周
LeetCode刷题1:第四周
LeetCode刷题1:第五周
LeetCode刷题1:第六周
LeetCode刷题1:第七周
LeetCode刷题1:第八周
前言
【week 7】Topic:二分查找
(1)367.有效的完全平方数
(2)剑指 Offer 53 - II. 0~n-1中缺失的数字
(3)162.寻找峰值
(4)33.搜索旋转排序数组
(5)1095.山脉数组中查找目标值
一、知识点
二分搜索是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
适用条件
- 二分查找是有条件的,首先是有序的,其次因为二分查找操作的是下标,所以要求是顺序。
- 最优时间复杂度:O(1), 最坏时间复杂度:O(logn)。
算法操作步骤
设给定的数组为A,元素为A[i],元素个数一共有n个,其中0<=i<=n-1,
1)设置查找中值A[mid],中值位置为mid=(low+high)/2;
2)如果要查找的值与A[mid]相等,那么就把这个值打印出来,算法结束;如果A[mid]的值较小,则设置low=mid+1;如果A[mid]的值较大,则设置high=mid-1;
3)重复第1、第2步骤,直到查找结束为止。
二、LeetCode例题
367. 有效的完全平方数
给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。
示例 1:
输入:16
输出:True
示例 2:
输入:14
输出:False
实现:
class Solution:
def isPerfectSquare(self, num: int) -> bool:
# 开方
return num**(1/2) == int(num**(1/2))
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
• 1 <= arr.length <= 10^5
• -100 <= arr[i] <= 100
实现:
关键点:
搞清楚当前状态量与当前位置元素之间的大小关系以及之后增加元素的影响(如下图)
是找出状态转移方程(变量)
本代码另外创建了DP列表,最后在DP列表中找最大值。这里还可以进一步优化,直接对nums本身进行操作,从而改善时间复杂度。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0] = nums[0]
for i in range(1,len(nums)):
dp[i] = max(nums[i], nums[i] + dp[i-1])
return max(dp)
162. 寻找峰值
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[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。
说明:
你的解法应该是 O(logN) 时间复杂度的。
实现:
解题思路:
(1)mid=0时需要和右侧比较,mid=n-1时和左侧比较;
(2)nums只要一个元素时,单独处理;
(3)峰值在中央时直接返回结果
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
# 折半查找
# mid==0时需要和右侧比较,mid==n-1时和左侧比较;
# nums只要一个元素时,单独处理;
# 峰值在中央时直接返回结果
if len(nums) == 1: return 0
left, right = 0, len(nums)-1
while left <= right:
mid = (left+right) // 2
if mid == 0:
if nums[mid] > nums[mid+1]:
return mid
else:
left = mid+1
elif mid == len(nums)-1:
if nums[mid] > nums[mid-1]:
return mid
else:
right = mid-1
elif nums[mid-1] < nums[mid] and nums[mid] > nums[mid+1]:
return mid
elif nums[mid] > nums[mid-1]:
left = mid+1
elif nums[mid] < nums[mid-1]:
right = mid-1
33. 搜索旋转排序数组
升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 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
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
• 1 <= nums.length <= 5000
• -10^4 <= nums[i] <= 10^4
• nums 中的每个值都 独一无二
• nums 肯定会在某个点上旋转
• -10^4 <= target <= 10^4
实现:
思路:
数组从任意位置劈开后,至少有一半是有序的。
基于这个事实。我们可以先找到哪一段是有序的 (只要判断端点即可),然后看 target 在不在这一段里,如果在,那么就把另一半丢弃。如果不在,那么就把这一段丢弃。
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 折半查找,分成两部分,查看左边还是右边有序(根据端点判断)
# 然后,看 target 在不在这一段里,如果在,那么就把另一半丢弃。如果不在,那么就把这一段丢弃
if not nums:
return -1
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 左半段有序
if nums[mid] >= nums[left]:
if nums[left] <= target <= nums[mid]:
right = mid - 1
else:
left = mid + 1
# 右半段有序
else:
if nums[mid] <= target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
1095. 山脉数组中查找目标值
(这是一个 交互式问题 )
给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。
如果不存在这样的下标 index,就请返回 -1。
何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:
首先,A.length >= 3
其次,在 0 < i < A.length - 1 条件下,存在 i 使得:
• A[0] < A[1] < … A[i-1] < A[i]
• A[i] > A[i+1] > … > A[A.length - 1]
你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:
• MountainArray.get(k) - 会返回数组中索引为k 的元素(下标从 0 开始)
• MountainArray.length() - 会返回该数组的长度
注意:
对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。此外,任何试图规避判题系统的解决方案都将会导致比赛资格被取消。
为了帮助大家更好地理解交互式问题,我们准备了一个样例 “答案”:https://leetcode-cn.com/playground/RKhe3ave,请注意这 不是一个正确答案。
示例 1:
输入:array = [1,2,3,4,5,3,1], target = 3
输出:2
解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。
示例 2:
输入:array = [0,1,2,4,2,1], target = 3
输出:-1
解释:3 在数组中没有出现,返回 -1。
提示:
• 3 <= mountain_arr.length() <= 10000
• 0 <= target <= 10^9
• 0 <= mountain_arr.get(index) <= 10^9
实现:
思路:三次二分查找
1. 先找到峰值;
2. 在左端升序序列二分查找,找到直接返回索引;
3. 在右端升序序列二分查找,发挥索引结果;
# """
# This is MountainArray's API interface.
# You should not implement it, or speculate about its implementation
# """
#class MountainArray:
# def get(self, index: int) -> int:
# def length(self) -> int:
class Solution:
def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
def binary_search(start, end, left=True):
# 三次二分查找
# 1.先找到峰值;
# 2.在左端升序序列二分查找,找到直接返回索引;
# 3.在右端升序序列二分查找,发挥索引结果;
while start <= end:
mid = (start + end) // 2
val = mountain_arr.get(mid)
if val == target:
return mid
elif val > target:
if left:
end = mid - 1
else:
start = mid + 1
else:
if left:
start = mid + 1
else:
end = mid - 1
return -1
n = mountain_arr.length()
left, right = 0, n - 1
while left <= right:
mid = (left + right) // 2
val = mountain_arr.get(mid)
if mid > 0 and val < mountain_arr.get(mid - 1):
right = mid - 1
elif mid < n - 1 and val < mountain_arr.get(mid + 1):
left = mid + 1
else:
break
index = binary_search(0, mid)
if index != -1:
return index
return binary_search(mid + 1, n - 1, False)