总结不代表是完全理解透彻,仅仅是记录自己的现阶段对知识的感悟,同时进行分享讨论
在进行算法的总结之前,先稍微总结一下二分查找使用到的python数据结构:列表。列表是零个或多个指向python数据对象的引用的有序集合
(1)列表运算
名称 | 符号 | 作用 |
---|---|---|
索引 | [ ] | 取序列中的某一个元素 |
连接 | + | 将两个列表中的元素连接起来 |
重复 | * | 多次连接 |
查询 | in | 查询列表中有没有某个元素 |
长度 | len | 查看列表中元素个数 |
切片 | [ : ] | 取出列表一部分 |
(2)列表方法
方法 | 用法 | 作用 |
---|---|---|
append | list.append(item) | 在列表的末尾添加一个元素 |
insert | list.insert(i,item) | 在列表的第i个位置插入一个元素 |
pop | list.pop() | 删除并返回列表最后一个元素 |
pop | list.pop(i) | 删除并返回列表中第i个位置的元素 |
sort | list.sort() | 将列表中的元素从小到大排序 |
reverse | list.reverse() | 将列表中元素反向 |
del | del list[i] | 删除列表中的第i个元素 |
index | list.index(item) | 返回item第一次出现时的下标 |
count | list.count(item) | 返回item在列表中出现的次数 |
remove | list.remove(item) | 移除列表中第一次出现的item |
列表大概就这么多的用法和知识点,外加一个range(n)是常用的一个方法,list(range(1,5,2))返回的是[1,3]
(1)704.二分查找
题目描述:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
代码:
class Solution(object):
def search(self, nums, target):
left = 0
right = len(nums)-1
while left <= right:
mid = (left+right)// 2
if nums[mid] < target:
left = mid+1
elif nums[mid] > target:
right = mid-1
else :
return mid
return -1
解释:
题目中给出有序数组和一个目标值,所以该题目是标准的二分查找题目,难点在于边界值的确定,那么我对边界值确定的解决办法是不论是第一次确定的区间还是不断压缩后的区间,都取闭区间。
第一步:根据题目确定了要使用二分法,那么需要确定列表的区间[0 : len(nums)-1],闭区间
第二步:通过while循环来把target在列表中和不在列表中两种情况分开,left和right的更新会不断压缩空间,如果left大于right后,即列表中不存在target,则返回-1
第三步:通过两条if语句不断更新left和right值来压缩空间,压缩后的空间也是闭区间,直到num[mid]等于target,到这里我们就找到了target在列表中的位置mid
(2)35.搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
代码:
class Solution(object):
def searchInsert(self, nums, target):
left = 0
right = len(nums)-1
while left <= right :
mid = (left+right)//2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid -1
else:
return mid
return left
解释:
还是传统的二分查找,和第一题思路一样,不同的地方在返回值,因为返回的条件是left大于right,那么target应该安放在right和left中间的位置(我没有写反哦),即x应该安插在,[…, right, left, …]中的[…, right,x, left, …]这个位置,此时x的安插的位置就是left所在的位置。
(3)69.Sqrt(x)
题目描述:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
代码:
class Solution(object):
def mySqrt(self, x):
if x == 0:
return 0
left = 0
right = x
while left <= right:
mid = (left+right)//2
if mid*mid < x:
left = mid +1
elif mid*mid > x:
right = mid -1
else:
return mid
return right
解释:
该题还有个苯办法,从0到x查找一个值的平方等于x,时间复杂度为O(n),空间复杂度为O(1),还有个简单的方法,使用Python的函数,可以参考leetcode官方题解,我为了算法一致性,还是继续用二分法写。
这道题的本质还是在0到x之间找一个整数,让它的平方等于x,前面还是使用经典二乘法进行逼近,亮点在于最后的return right,是right的原因是因为最终right是一个比left小的整数,如mysqrt(8)应返回2而不是3。即Sqrt(x)应该等于[…, right, left,…]中的right
(4)367. 有效的完全平方数
题目描述:
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
进阶:不要 使用任何内置的库函数,如 sqrt 。
代码:
class Solution(object):
def isPerfectSquare(self, num):
"""
:type num: int
:rtype: bool
"""
left = 0
right = num
while left <= right:
mid = (left+right)//2
if mid*mid < num:
left = mid +1
elif mid*mid > num:
right = mid-1
else:
return True
return False
解释:
和上一题本质上一样,都是找平方数,比上一题更简单,找的到就返回True,找不到就返回False
二分查找的本质是通过不断收缩边界来更新搜索空间进而找到目标元素所在的位置,听起来挺绕口的,通过代码来说明比较清晰
代码模板:
#我们假设查找的范围空间是闭区间[A:B]
#nums是单调增加的数组,且target在区间内只出现一次
left = A
right = B
#循环内的程序在不断的缩小left和right的范围,只要left比right小,我们就可以缩小,最终的结果是相等或者left大于right。
while left <= right:
#mid就是用来不断更新下边界和上边届
mid = (left+right)//2
if nums[mid] < target:
#当现在的中间值小于target时候,我们就把下边界的值更新为mid+1,不更新为mid是因为nums[mid]必然不等于target
#总的查找范围就变成了[(left+right)//2 +1, right],范围缩小了一半
left = mid +1
if nums[mid] > target
#当现在的中间值大于target时候,我们就把上边界的值更新为mid-1,不更新为mid是因为nums[mid]必然不等于target
#总的查找范围就变成了[(left+right)//2 +1, right],范围缩小了一半
right = mid -1
else:
#此时nums[mid]=target,那么我们就找到这个target所在的位置了
return mid
#如果下边界大于上边界了依然没有找到target,那么我们就该根据题目需要返回不同的值
#返回left或者right是根据我们需要返回值的大小来决定的,此时的left大于right
#表现为[..., right, left,...]
return False or left or right
写在最后,之前断断续续的刷过一些题,因为没有归纳整理,过一段时间后可能就忘记了,所以现在准备按照Carl大佬写的代码随想录重新系统的刷一遍leetcode,最后通过博客进行总结归纳,时常看看,如果有新的感悟也方便更新。