在听这节课之前,需要先听下二分查找 的基础知识。
我基本明白了二分查找的套路。
尤其是前一篇文章要好好啃下来,弄明白 红蓝染色法 是怎么弄的。
- 1 <= nums.length <= 1000
- -231 <= nums[i] <= 231 - 1
- 对于所有有效的 i 都有 nums[i] != nums[i + 1]
搞不明白 搞不明白 不会做 !!!
我要搞定它,弄明白 弄明白 !
这题我看了好多题解,实在不懂。后来又个小伙子的评论我看了就懂了。
也许很多人看了,还是不懂,图解也只是迷迷糊糊根据答案来,我这里通俗解释以下,这道题,最最最重要的是条件,条件,条件,两边都是负无穷,数组当中可能有很多波峰,也可能只有一个,如果尝试画图,就跟股票信息样,没有规律,如果根据中点值判断我们的二分方向该往何处取,这道题还有只是返回一个波峰。你这样想,中点所在地方,可能是某座山的山峰,山的下坡处,山的上坡处,如果是山峰,最后会二分终止也会找到,关键是我们的二分方向,并不知道山峰在我们左边还是右边,送你两个字你就明白了,爬山(没错,就是带你去爬山),如果你往下坡方向走,也许可能遇到新的山峰,但是也许是一个一直下降的坡,最后到边界。但是如果你往上坡方向走,就算最后一直上的边界,由于最边界是负无穷,所以就一定能找到山峰,总的一句话,往递增的方向上,二分,一定能找到,往递减的方向只是可能找到,也许没有。
山峰的概念,左边的元素比它小,右边的元素也比它小。
题目中有个含义 可以假设 nums[-1] = nums[n] = -inf, 那么最后一个元素一定是下坡状态。
我们按照上坡的状态才能找到山峰的位置, 所以可以把比较公式 nums[m] < nums[m+1], 满足条件的为true, 涂蓝色,R = m-1, 不满足条件,设置为false,下坡状态, 设置为红色。不断循环遍历。 找到第一个满足条件的边界,就是我们要求的解。
解法
1️⃣ 暴力解法: 一次遍历 ,判断前后两个元素是否比当前元素小,如果是,返回下标。 时间复杂度 O(n), 不满足题意要求,需要设计O(log(n) 时间复杂度。
2️⃣ 二分查找, 红蓝染色法。
设置两个指针 L R ,分别指向 L = 0 , R = n -2 ,使用左闭右闭的区间。
( 这里说明下为什么 R= n-2 ,因为 nums[n] = -inf, nums[n-1] 一定是下坡的,它可能是个峰值元素,如果整个数组是单调递增的。)
M = (L + R)//2 , 比较 nums[M] 和 nums[M+1] ,
如果nums[M] < nums[M+1] ,判断的是M这个元素,这个元素 比上个元素小,那它是上坡状态, 它一定不是峰值元素, 让M 左边的元素 都涂红色,L = M +1 ,根据区间不变量, M-1 也都涂红色。
如果 nums[M] > nums[M+1] , 判断是M这个元素涂什么颜色, 这个元素比下个元素大,它很可能是峰值,也可能是下坡的半山腰, 这个元素涂蓝色,继续往中间部分去找。 M +1 都涂蓝色。
如此循环迭代。
如果还是不懂,可以手动去涂色,多找几个例子,不停的画画,就能找到感觉了。
看代码:
class Solution(object):
def findPeakElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left = 0
right = len(nums) -2
while left <= right:
mid = (left + right)//2
if nums[mid] < nums[mid+1]: # 上坡状态,需要往后找。
left = mid +1
else: # 下坡或峰值,需要往前找。
right = mid - 1
return left
so = Solution()
nums = [1,2,3,1] #[1,2,1,3,5,6,4]
print(so.findPeakElement(nums))
题解
1️⃣ 暴力解法: 一次遍历,设置一个变量min,遍历所有的数字,和min对比,如果比min小,更新。 时间复杂度为O(n) , 空间复杂度为O(1), 不满足题意要求。
2️⃣ 二分查找。
设置 两个指针,L R 使用闭区间, L = 0, R = len(nums) - 2 , 和最后一个元素对比,
mid = (L +R ) // 2
- 如果nums[mid] < nums[-1] ,移动right = mid - 1, 也就是最小元素不在 [mid, right] 中,在[left, mid] 中。
- 如果nums[mid] < nums[-1] , 移动left = mid + 1, 也就是最小元素不在 [left, mid] 中,在[mid, right] 中。
如果想不清楚,找个例子画一画就好了.
代码
class Solution(object):
def findMin(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left = 0
right = len(nums) - 2
while left <= right:
mid = (left + right) // 2
if nums[mid] > nums[-1]:
left = mid + 1
else:
right = mid - 1
return nums[left]
nums = [4,5,6,7,0,1,2]
so = Solution()
print(so.findMin(nums))
解法:
二分法。
这题是要找到target的索引。
- 先找到最小元素;
- 在正确的部分,二分查找,找到其中的索引。
两次二分查找即可,思路清晰明了。
直接看代码吧:
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
i = self.findMin(nums)
if target > nums[-1]:
left , right = 0, i-1
else:
left , right = i, len(nums)-1
return self.lower_bound(nums, left, right, target)
def findMin(self,nums):
""" 获取最小元素
"""
left = 0
right = len(nums) - 2
while left <= right:
mid = (left + right)//2
if nums[mid] > nums[-1]:
left = mid + 1
else:
right = mid - 1
return left
def lower_bound(self, nums, left, right, target):
"""
在 left ... right 这个数组中找
"""
while left <= right :
mid = (left + right)//2
if nums[mid] < target : #
left = mid + 1
else:
right = mid - 1
return left if nums[left] == target else -1
nums = [4,5,6,7,0,1,2]
target = 0
so = Solution()
print(so.search(nums, target))
x 的平方根
69. x 的平方根 这题也经常考。
这里写一写,如果写不对,debug一下。这里面有个难点。
class Solution(object):
def mySqrt(self, x):
"""
:type x: int
:rtype: int
"""
left = 1
right = x
while left <= right: # 左闭右闭区间
mid = (left + right) //2
if mid * mid == x:
return mid
elif mid * mid < x: # 左侧涂红色,
left = mid + 1
else: # 涂蓝色
right = mid - 1
return left-1 # 可以手动遍历一下就知道。
so = Solution()
print(so.mySqrt(24))