算法训练Day01 Leetcode704.二分查找

0. 学习资料来源

题目链接:力扣icon-default.png?t=M85Bhttps://leetcode.cn/problems/binary-search/

文章讲解:代码随想录代码随想录PDF,代码随想录百度网盘,代码随想录知识星球,代码随想录八股文PDF,代码随想录刷题路线,代码随想录知识星球八股文https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html

视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili《代码随想录》算法公开课开讲啦!快来打卡!代码随想录刷题网站:programmercarl.com, 视频播放量 33781、弹幕量 398、点赞数 1946、投硬币枚数 1762、收藏人数 1032、转发人数 104, 视频作者 代码随想录, 作者简介 我是Carl,哈工大师兄,先后在腾讯和百度从事一线技术研发的程序员,公众号「代码随想录」,相关视频:学历低于“211”建议不要去做算法!,Leetcode力扣 1-300题视频讲解合集|手画图解版+代码【持续更新ing】,华为算法大佬耗时200小时录制:Leetcode刷题100道,足以吊打字节面试官!,二分查找为什么总是写错?,LeetCode刷了1200题的面试官揭秘刷题面试找工作,算 法 辅 导,人人都能看得懂的Leetcode力扣刷题教程合集(最后更新:695 .岛屿的最大面积),硬核!一周刷爆LeetCode,算法大神(左程云)耗时112天打造出算法与数据结构基础到高级全家桶教程+大厂面试真题详解,【强烈推荐】深入浅出数据结构 - 顶尖程序员图文讲解 - UP主翻译校对 (已完结),民办三本,6个计算机毕业生,毕业1年后现状https://www.bilibili.com/video/BV1fA4y1o715

1. 学习笔记

这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

二分法的主要思路是初始时,左右指针分别指向头部和尾部,

然后算出middle指针的值,和target比较,

如果大了,取左半边重复上述操作,如果小了,取右半边重复上述操作 直至while条件不满足,返回-1 或者找到target为止

主要思路如上,不是很复杂,但是其中细节的边界条件是很容易搞错。

循环不变量是区间的定义,代码的边界也要根据区间的定义写好。

 Example1:找得到元素的例子

 Example2.找不到元素的例子

 

二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?

大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

 代码实现

二分法第一种写法,定义target在左闭右闭的区间里

第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

 

# 左闭右闭 迭代法
# time complexity: O(logN)
# space complexity: O(1)

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums)-1
        while left <= right:
            middle = (left+right)//2
            potentialMatch = nums[middle]
            if potentialMatch == target:
                return middle
            elif potentialMatch > target:
                right = middle-1
            elif potentialMatch < target:
                left = middle + 1
        return -1

# 左闭右闭 递归法
# time complexity: O(logN)
# space complexity: O(logN) ?not sure

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        def binarySearch(nums,target,left,right):
            if left > right:
                return -1
            middle = (left+right)//2
            potentialMatch = nums[middle]
            if potentialMatch == target:
                return middle
            elif potentialMatch > target:
                return binarySearch(nums,target,left,middle-1)
            elif potentialMatch < target:
                return binarySearch(nums,target,middle+1,right)
        return binarySearch(nums,target,0,len(nums)-1)
   

二分法第二种写法

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

在数组:1,2,3,4,7,9,10中查找元素2,如图所示:(注意和方法一的区别

 

# 左闭右开 迭代法
# time complexity: O(logN)
# space complexity: O(1)

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left = 0
        right = len(nums)
        while left < right:
            middle = (left+right)//2
            potentialMatch = nums[middle]
            if potentialMatch == target:
                return middle
            elif potentialMatch > target:
                right = middle
            else:
                left = middle + 1
        return -1

# 左闭右开 递归
# time complexity: O(logN)
# space complexity: O(1)
class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        def binarySearch(nums,target,left,right):
            if left >= right:
                return -1
            middle = (left+right)//2
            potentialMatch = nums[middle]
            if potentialMatch == target:
                return middle
            elif potentialMatch > target:
                return binarySearch(nums,target,left,middle)
            else:
                return binarySearch(nums,target,middle+1,right)
        return binarySearch(nums,target,0,len(nums))

2. 自己看到题目的第一想法

用二分法,循环的判断选择要处理的区间,因为之前做过,所以说知道要坚持区间不变量,没看解析之前自己写的代码如下:

# 第一遍自己做
# 方法一:左闭右闭区间

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        def binarySearch(nums,target,left,right):
            # 因为是左闭右闭区间,所以这里的left=right的情况才有意义
            while left <= right:
                middle = (left+right)//2
                potentialMatch = nums[middle]
                if potentialMatch == target:
                    return middle
                # 因为左闭右闭,所以这里right直接变成middle-1了
                if potentialMatch > target:
                    right = middle -1
                # 因为左闭右闭,所以这里left直接变成middle+1了
                if potentialMatch < target:
                    left = middle + 1
            return -1
        return binarySearch(nums,target,0,len(nums)-1)

# 方法二:左闭右开区间

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        def binarySearch(nums,target,left,right):
            while left < right:
                middle = (left+right)//2
                potentialMatch = nums[middle]
                if potentialMatch == target:
                    return middle
                if potentialMatch > target:
                    right = middle
                if potentialMatch < target:
                    left = middle + 1
            return -1
        return binarySearch(nums,target,0,len(nums)) 
        # 第一遍做的时候这里写错了,len(nums)写成了len(nums)-1
        # 没有从头到尾贯彻区间不变量

自己写的有如下几个问题:
1. 总体来说是用的迭代法,但是抽象了另一个函数,其实是没必要的,要么递归,要么迭代

2. 没有彻底的贯彻区间不变量,在初始化left和right指针的时候也要坚持区间不变量,在左闭右开的时候,right初始要定义成len(nums)

3. 看完代码随想录想法

学习完代码随想录的文章和视频之后,我感觉整个代码的思路顿时清晰,不会因为<还是<=而迷茫,middle+1还是-1,还是不加不减而迷茫,在思考这些问题的时候,直接从定义的区间是什么来出发去思考,就不会写错。别忘了初始化指针的时候,就开始要遵循区间不变量了。

4.学习总结

这道题大概花了2个小时的时间,虽然写过,但是重新写的时候仍然在重要的细节上犯错,区间不变量原则贯彻不彻底。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值