【二分查找】LeetCode题目集

文章详细介绍了二分查找算法在解决力扣(LeetCode)中多个问题的应用,包括基本的查找索引、搜索插入位置、最长递增子序列以及在排序和旋转数组中查找元素的方法。不同题目中,二分查找被灵活运用,有时需要结合其他策略如处理旋转数组的特殊情况。
摘要由CSDN通过智能技术生成

二分查找基本代码

# 返回 x 在 arr 中的索引,如果不存在返回 -1
def binarySearch (arr, l, r, x): 
  
    # 基本判断
    if r >= l: 
  
        mid = int(l + (r - l)/2)
  
        # 元素整好的中间位置
        if arr[mid] == x: 
            return mid 
          
        # 元素小于中间位置的元素,只需要再比较左边的元素
        elif arr[mid] > x: 
            return binarySearch(arr, l, mid-1, x) 
  
        # 元素大于中间位置的元素,只需要再比较右边的元素
        else: 
            return binarySearch(arr, mid+1, r, x) 
  
    else: 
        # 不存在
        return -1

力扣704题即为简单的二分查找题:704
力扣上用到二分查找的相关题目有:35题搜索插入位置:
力扣35题
题解代码如下链接

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:


        low, high = 0, len(nums) - 1

        # 在 [low, high] 找 target
        while low <= high:

            mid = (low + high) // 2

            # 如果 target 找到,直接返回位置
            if nums[mid] == target:
                return mid
            # 如果 target 大于中间数,则 target 可能在右区间
            # 在 [mid + 1, right] 中找
            elif nums[mid] < target:
                low = mid + 1
            # 如果 target 小于中间数,则 target 可能在左区间
            # 在 [left, mid -1] 中找
            else:
                high = mid - 1
        
        # 如果在数组中没找到,则返回需要插入数值的位置
        return high + 1

力扣300题:最长递增子序列
这个解法我暂时没有看懂(明白了,意思就是用二分法继续接上前面的子序列,看看能不能形成新的更长子序列):

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        
        # 记录每种长度末尾的最小值
        length_to_min = [0] * (len(nums) + 1)
        length_to_min[0] = float("-inf")
        length = 0 # 目前最长递增子序列长度
        for i in range(len(nums)):
            if nums[i] > length_to_min[length]:
                # 当前的值比目前最长递增子序列的末尾最小值要大
                # 所以把这个值拼在这个递增子序列后面,可以组成更长的递增子序列
                # 其长度为legnth+1,末尾最小值为nums[i]
                length += 1 
                length_to_min[length] = nums[i]
            else:
                # 当前的值比目前最长递增子序列的末尾最小值要小
                # 不能组成更长的递增子序列
                # 但是该值可能可以更新较短长度的递增子序列的末尾最小值
                # 在数组length_to_min向前找到第一个比他小的值,更新该长度的后一个长度的值
                # 数组length_to_min是递增的,因此可以使用二分查找,证明如下
                '''
                    假设i < j,且length_to_min[i] >= length_to_min[j]
                    那长度为j的递增子序列里一定存在一个长度为i的子序列,且末尾值小于length_to_min[i]
                    与条件矛盾
                '''
                left, right = 1, length
                while left <= right:
                    mid = (right - left) // 2 + left
                    if length_to_min[mid] < nums[i]:
                        left = mid + 1
                    else:
                        if length_to_min[mid-1] < nums[i]:
                            length_to_min[mid] = nums[i]
                            break
                        else:
                            right = mid - 1
        return length

力扣34:在排序数组中查找元素的第一个和最后一个位置力扣34
自己用上面的二分法求解时候超出时间限制,相关题解为:二分法进一步的求解

class Solution(object):
	def searchRange(self, nums, target):
		if not nums:
			return [-1,-1]
		n = len(nums)
		# 查找第一个和最后一个元素
		def find(is_find_first):
			begin = 0
			end = n-1
			# if和elif的逻辑跟正常的二分查找一样
			while begin<=end:
				mid = begin+(end-begin)//2
				if nums[mid]>target:
					end = mid-1
				elif nums[mid]<target:
					begin = mid+1
				# 找到目标值了,开始定位到第一个和最后一个位置	
				else:
					# 查找第一个和最后一个逻辑很类似,这里用一个变量标记
					# 是查找第一个还是查找最后一个
					if is_find_first:
						# 如果不满足条件,缩小右边界,继续往左边查找
						if mid>0 and nums[mid]==nums[mid-1]:
							end = mid-1
						else:
							return mid
					else:
						# 如果不满足条件,增大左边界,继续往右边查找
						if mid<n-1 and nums[mid]==nums[mid+1]:
							begin = mid+1
						else:
							return mid
			return -1
		return [find(True), find(False)]

力扣33:搜索旋转排序数组 力扣33题
题解1:先找断点再用二分

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 先用类似二分的方法寻找分界点
        left = 1
        right = len(nums) - 1
        while (left <= right):
            mid = (left + right) // 2
            if nums[mid] < nums[0]:
                right = mid - 1
            else:
                left = mid + 1
        diving_line =left

        if nums[0] == target:
            return 0
        elif nums[0] > target:  # 这种情况target应该处于另一个区域
            if self.binary_search(nums[diving_line:], target) != -1:
                return self.binary_search(nums[diving_line:], target) + diving_line
            else:
                return -1
        else:
            return self.binary_search(nums[0:diving_line], target)

    #标准的二分查找找某个特定的值
    def binary_search(self, list, target):
        left = 0
        right = len(list) - 1
        while (left <= right):
            mid = (left + right) // 2
            if list[mid] == target:
                return mid
            elif list[mid] > target:
                right = mid - 1
            else:
                left = mid + 1
        return -1

题解2:直接原地操作

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        L, R = 0, len(nums) - 1
        while L <= R:
            M = L + (R - L) // 2
            if nums[M] == target:
                return M
            elif nums[L] <= nums[M]: # L ~ M 有序
                if nums[L] <= target < nums[M]: # target 存在于 有序数组中,二分
                    R = M - 1
                else: # target 不存在于 L ~ M中,到右边去找
                    L = M + 1
            else: # M ~ R有序
                if  nums[M] < target <= nums[R]: # target 存在于 有序数组中,二分
                    L = M + 1
                else: # target 不存在于 M ~ R中,到左边去找
                    R = M - 1
        return -1

二分查找最强总结:内功心法加模版

力扣81题搜索旋转数组
用力扣33题的两种解法均不能解决,相关题解如下:
题解33题
最值得关注的点在于continue命令,直接避免了当nums[left]=nums[right]时候不知道target在mid左边还是右边的情况

class Solution(object):
    def search(self, nums, target):
        if not nums: return -1
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (left + right) / 2
            if nums[mid] == target:
                return True
            if nums[left] == nums[right]:
                left += 1
                continue
            if nums[mid] <= nums[right]:
                if target > nums[mid] and target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
            else:
                if target < nums[mid] and target >= nums[left]:
                    right = mid - 1
                else:
                    left = mid + 1
        return False

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值