数据结构与算法系列之二分算法

二分查找算法

二分法是一种快速查找算法,标准二分查找算法时间复杂度lg(n)。下面我们通过一些例子来进行二分查找算法的解读。

二分查找标准算法

	leetcode的704题:
	给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/binary-search/xexoac/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这题是标准的二分查找算法,大家估计都清楚,给定非空有序数组array(假设是升序)与寻找的目标target,主要思路如下:
	1、两个指针,左右指针left、right,其中将left指针指向位置0,right指针指向末尾len(array) - 1
	2、确定中间指针mid,mid = left + (right - left) / 2
	3、然后判断array[mid] 与 target 的大小,有3中情况:
		- 相等,此时直接返回mid就好
		- 如果 array[mid] > target,说明此时mid所在位置的数比 target 要大,此时,right移动到mid
		- 如果 array[mid] < target,说明此时mid所在位置的数比 target 要小,此时,left移动到mid
	4、重复以上步骤直到left > right 或者找到target为止

Java代码和Python代码分别如下:

class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) 
            return -1;
        if (nums[0] > target || nums[nums.length -1] < target) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            else if (nums[mid] > target) {
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        return -1;
    }
}
class Solution:
   def search(self, nums: List[int], target: int) -> int:
       if nums is None or len(nums) == 0:
           return -1
       if nums[0]>target or nums[-1] < target:
           return -1
       left, right = 0, len(nums) - 1
       while left <= right:
           mid = left + (right - left) // 2
           if nums[mid] == target:
               return mid
           elif nums[mid] > target:
               right = mid-1
           else:
               left = mid+1
       return -1

模版1

int binarySearch(vector<int>& nums, int target){
 if(nums.size() == 0)
   return -1;

 int left = 0, right = nums.size() - 1;
 while(left <= right){
   // Prevent (left + right) overflow
   int mid = left + (right - left) / 2;
   if(nums[mid] == target){ return mid; }
   else if(nums[mid] < target) { left = mid + 1; }
   else { right = mid - 1; }
 }

 // End Condition: left > right
 return -1;
}
int binarySearch(int[] nums, int target){
  if(nums == null || nums.length == 0)
    return -1;

  int left = 0, right = nums.length - 1;
  while(left <= right){
    // Prevent (left + right) overflow
    int mid = left + (right - left) / 2;
    if(nums[mid] == target){ return mid; }
    else if(nums[mid] < target) { left = mid + 1; }
    else { right = mid - 1; }
  }

  // End Condition: left > right
  return -1;
}
def binarySearch(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    if len(nums) == 0:
        return -1

    left, right = 0, len(nums) - 1
    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    # End Condition: left > right
    return -1

LC题目

lc79

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。


示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
 

提示:

你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题目相对比较简单和基础,直接套用上述模版就好了。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if nums is None or len(nums) == 0:
            return -1
        
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid 
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return -1

这道题目数组是有限长度了,如果数组是无限长度的呢?
那么请看下一道题。

LC702

给定一个升序整数数组,写一个函数搜索 nums 中数字 target。如果 target 存在,返回它的下标,否则返回 -1。注意,这个数组的大小是未知的。你只可以通过 ArrayReader 接口访问这个数组,ArrayReader.get(k) 返回数组中第 k 个元素(下标从 0 开始)。

你可以认为数组中所有的整数都小于 10000。如果你访问数组越界,ArrayReader.get 会返回 2147483647。

 

样例 1:

输入: array = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 存在在 nums 中,下标为 4
样例 2:

输入: array = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不在数组中所以返回 -1
 

注释 :

你可以认为数组中所有元素的值互不相同。
数组元素的值域是 [-9999, 9999]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-a-sorted-array-of-unknown-size
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题目其实相当于是将有限数组变成了无限长度数组,解题思路类似,如果遍历查找时注意剪枝。
假设一种场景:
如果说,我们要求的目标数不在这个无限数组中,那么可以直接剪枝掉返回-1,否则会一直寻找下去造成超时。

直接遍历+剪枝

时间复杂度O(n),空间复杂度O(1)

# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
#    def get(self, index: int) -> int:

class Solution:
    def search(self, reader, target):
        """
        :type reader: ArrayReader
        :type target: int
        :rtype: int
        """
        i = 0
        if reader.get(i) == 2147483647:
            return -1
        while reader.get(i) != target:
            if reader.get(i) == 2147483647:
                return -1
            i += 1
        return i

二分查找

这里二分查找的思路相对难找一些,这里总结一下几个方法。

从题目本身条件出发,创造条件进行二分查找

我们知道,二分查找离不开两个条件:一是数组有序,另一个是依据mid与目标值的大小关系来确定左右边界。
目前,数组有序这个条件已经满足了。接下来,需要创造依据mid与目标值的大小关系来确定左右边界的条件。满足这个条件需要有初始边界就好。
该数组是无限数组,如何创造边界呢?题目中有两个假设条件:
注释 :

  • 你可以认为数组中所有元素的值互不相同。
  • 数组元素的值域是 [-9999, 9999]。
    因此,可以理解成数组的最大长度为 19999(9999*2+1)。
    同时,如果越界,数值达到 2147483647,超过数组元素最大数值了,因此,即使越界也可以直接进行比较。
    然后就可以套用二分模版了,左边界为0,右边界19999(包含),然后开始二分。
# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
#    def get(self, index: int) -> int:

class Solution:
    def search(self, reader, target):
        """
        :type reader: ArrayReader
        :type target: int
        :rtype: int
        """
        left, right = 0, 19999
        if reader.get(0) == 2147483647:
            return -1
        while left <= right:
            mid = left + (right - left) // 2
            if reader.get(mid) == target:
                return mid
            elif reader.get(mid) < target:
                left = mid + 1
            else:
                right = mid-1
        return -1   
泛化解法,创造条件进行二分查找

由上一条分析,主要是为了创造比较的条件。
那么,无限数组怎么变成有限的呢?可以用区间长度为1的区间(即元素)进行比较,区间为左闭右开区间。
此时,解法更泛化一些,保证数组有序就好了。
这里有两种写法:

# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
#    def get(self, index: int) -> int:

class Solution:
    def search(self, reader, target):
        """
        :type reader: ArrayReader
        :type target: int
        :rtype: int
        """
        left, right = 0, 1
        if reader.get(0) == 2147483647:
            return -1
        while reader.get(right) < target:
            left = right
            right = right * 2
        while left <= right:
            mid = left + (right - left) // 2
            if reader.get(mid) == target:
                return mid
            elif reader.get(mid) < target:
                left = left + 1
            else:
                right = right - 1
        return -1   
# """
# This is ArrayReader's API interface.
# You should not implement it, or speculate about its implementation
# """
#class ArrayReader:
#    def get(self, index: int) -> int:

class Solution:
    def search(self, reader, target):
        """
        :type reader: ArrayReader
        :type target: int
        :rtype: int
        """
        left, right = 0, 1
        if reader.get(0) == 2147483647:
            return -1
        while left <= right:
            mid = left + (right - left) // 2
            if reader.get(mid) == target:
                return mid
            elif reader.get(mid) < target:
                left = left + 1
                right = right * 2
            else:
                right = right - 1
        return -1   
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值