LeetCode小白算法成长记之二分查找
Posted ydongy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode小白算法成长记之二分查找相关的知识,希望对你有一定的参考价值。
不积跬步,无以至千里;不积小流,无以成江海。
前言
内容主要是个人学习使用,题目分类以及部分参考资料来自于CyC的博客,非常感谢大佬,题目来源于LeetCode,非常感谢本站支持。
二分查找
二分查找又称折半查找,顾名思义就是每查找比较一次,就会去掉一半的不匹配项,重复执行此步骤直到找到目标元素或者不可以在分割
69. x 的平方根(Easy)??
实现?int sqrt(int x)?函数。
计算并返回?x?的平方根,其中?x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
? 由于返回类型是整数,小数部分将被舍去。
解题思路:
计算平方根可以采用多种方法,具体可参考LeetCode官方,
- 对于(sqrt{x})的值必定存在于0~x,更具体应该是0~(frac{x}{2}),所以采用二分法在此区间查找
- 对于(sqrt{8}) = 2.82842...,最后应该返回 2 而不是 3。
代码实现
class Solution:
def mySqrt(self, x: int) -> int:
if (x <= 1): # 处理0,1特殊情况
return x;
left = 1
right = x // 2 # 确定区间
while left <= right:
mid = left + (right-left) // 2
num = mid * mid
if num > x:
right = mid - 1
elif num < x:
left = mid + 1
elif num == x:
return mid
return right
744. 寻找比目标字母大的最小字母(Easy)??
给你一个排序后的字符列表letters
,列表中只包含小写英文字母。另给出一个目标字母?target
,请你寻找在这一有序列表里比目标字母大的最小字母。
在比较时,字母是依序循环出现的。举个例子:
- 如果目标字母
target = ‘z‘
并且字符列表为letters = [‘a‘, ‘b‘]
,则答案返回‘a‘
示例:
输入:
letters = ["c", "f", "j"]
target = "a"
输出: "c"
输入:
letters = ["c", "f", "j"]
target = "c"
输出: "f"
解题思路:
- 由于数组有序,可利用二分法查找
target
- 当目标值大于等于mid值(中间值),改变区间的左边界值
- 否则,改变区间的右边界值,最终如果遍历所有字符都没有找到满足要求的值,则输出第一个字符
代码实现
class Solution:
def nextGreatestLetter(self, letters: List[str], target: str) -> str:
left = 0
right = len(letters) - 1
while left <= right:
mid = left + (right - left) // 2
if letters[mid] > target:
right = mid - 1
elif letters[mid] < target:
left = mid + 1
else:
left = mid + 1
return letters[left] if left < len(letters) else letters[0]
540. 有序数组中的单一元素(Medium)??
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2
示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10
注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。
解题思路:
- 有序数组中存在一个单一元素,所以数组个数必定是奇数,可以利用二分法分割数组
- 分割后的数据,左右两边元素个数相同,举例:
- 当分割后的左右数组都是偶数个数:
[1,1,2,2,3,3,4,5,5]
,该数组分割后前[1,1,2,2]
,后为[3,4,5,5]
,由于都是偶数个,所以必定有一组是不含单一元素的,我们只需要比较中间元素和前一元素是否相等,不想等的则是正确一组,反之是需要下次查找 - 当分割后的左右数组都是奇数个数:
[3,3,7,7,10,11,11]
,该数组分割后前[3,3,7]
,后为[10,11,11]
,由于都是奇数个,所以不能确定那一组到底是包含单一元素,我们仍然通过比较中间元素和前一元素是否相等,则相等则表示那一组是正确的,反之不正确。
- 当分割后的左右数组都是偶数个数:
- 通过2.可以看出来,分割为偶数,中间元素与前一元素不相等,表示左边一组正确,右边一组错误,分割为奇数,中间元素与前一元素相等,表示左边一组正确,右边一组错误,
代码实现
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
tag = len(nums[:mid]) % 2 == 0 # 判断分割的左侧是不是偶数对
if tag:
if nums[mid] == nums[mid - 1]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] == nums[mid - 1]:
left = mid + 1
else:
right = mid - 1
return nums[right]
278. 第一个错误的版本(Easy)??
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用?bool isBadVersion(version)?接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5)?-> true
调用 isBadVersion(4)?-> true
所以,4 是第一个错误的版本。?
解题思路:
- 根据题意可知,当我们随机获取几个版本,如果当前版本是正确的,则表示该版本之前的版本都是正确版本,其实就是一个查找最左边界的问题
- 当
mid
版本是正确的,则表示错误版本处于[mid+1,right]
,不断缩减右区间 - 当
mid
版本是错误的,则表示错误版本处于[left,mid]
,不断缩减左区间
代码实现
class Solution:
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
left = 1
right = n
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
153. 寻找旋转排序数组中的最小值 (Medium)??
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组?[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
解题思路:
- 通过题目可以发现,数组经过旋转之后,左边和右边依旧是有序的
- 每次循环只需要比较 nums[mid] 和 nums[-1] 的大小关系。
- 对于例子:
[4, 5, 6, 7, 0, 1, 2], mid = 3, nums[mid] = 7, nums[-1] = 2
, 故nums[mid] > nums[-1]
,说明数组旋转后较小的一部分升序序列在mid右边,更新区间:left = mid + 1
; - 对于例子:
[6, 7, 1, 2, 3, 4, 5], mid = 3, nums[mid] = 2, nums[-1] = 5
,故nums[mid] < nums[-1]
,说明数组旋转后较小的一部分升序序列在mid左边,更新区间:right = mid - 1
。
- 对于例子:
代码实现
class Solution:
def findMin(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > nums[-1]:
left = mid + 1
else:
right = mid - 1
return nums[left]
34. 在排序数组中查找元素的第一个和最后一个位置(Medium)??
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是?O(log n) 级别。
如果数组中不存在目标值,返回?[-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例?2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
解题思路:
- 根据题意得知,寻找一个数的左边界和右边界,所以可以才用两次二分查找
代码实现
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = 0
right = len(nums) - 1
ret = []
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
if left == len(nums):
ret.append(-1)
else:
ret.append(left if nums[left] == target else -1)
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
else:
left = mid + 1
if left == 0:
ret.append(-1)
else:
ret.append(left-1 if nums[left-1] == target else -1)
return ret
以上是关于LeetCode小白算法成长记之二分查找的主要内容,如果未能解决你的问题,请参考以下文章