【面试】每日力扣题 Day5——二分法 1

二分法基本步骤

  1. 初始化边界:
    • 定义左边界 left 和右边界 right,通常初始化为数组的起始索引和结束索引。
  2. 计算中间位置:
    • 计算中间位置 mid,通常使用公式 left + (right - left) // 2
  3. 比较中间元素:
    • 将中间位置的元素 arr[mid] 与目标元素 target 进行比较。
    • 如果 arr[mid] == target,则找到目标元素,返回 mid
    • 如果 arr[mid] < target,则目标元素在右半部分,更新左边界 left = mid + 1
    • 如果 arr[mid] > target,则目标元素在左半部分,更新右边界 right = mid - 1
  4. 重复步骤 2 和 3:
    • 不断缩小查找范围,直到左边界超过右边界,表示目标元素不存在于数组中。

35、搜索插入位置

链接:35、搜索插入位置

题目描述:
在这里插入图片描述

方法 1

# 方法 1:二分法(闭区间)
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
	    # 初始化左边界和右边界
	    left, right = 0, len(nums) - 1
	    
	    # 开始二分查找
	    while left <= right:
	        # 计算中间位置,避免直接使用 (left + right) // 2 可能导致的整数溢出
	        mid = left + (right - left) // 2
	        
	        # 如果中间元素等于目标值,返回中间位置
	        if nums[mid] == target:
	            return mid
	        # 如果中间元素小于目标值,目标值在右半部分,更新左边界
	        elif nums[mid] < target:
	            left = mid + 1
	        # 如果中间元素大于目标值,目标值在左半部分,更新右边界
	        else:
	            right = mid - 1
	    
	    # 如果目标值不存在于数组中,返回插入位置
	    return left

代码解释

  1. 初始化边界:
    • left 初始化为 0,表示数组的起始索引。
    • right 初始化为 len(nums) - 1,表示数组的结束索引。
  2. 循环查找:
    • 使用 while left <= right 循环,确保查找范围有效。
    • 计算中间位置 mid,使用公式 mid = left + (right - left) // 2,避免直接使用 (left + right) // 2 可能导致的整数溢出。
  3. 比较中间元素:
    • 如果 nums[mid] == target,则找到目标元素,返回 mid。
    • 如果 nums[mid] < target,则目标元素在右半部分,更新左边界 left = mid + 1。
    • 如果 nums[mid] > target,则目标元素在左半部分,更新右边界 right = mid - 1。
  4. 返回插入位置:
    • 如果循环结束后仍未找到目标元素,返回 left,表示目标值的插入位置。

复杂度分析

  • 时间复杂度:O(log n),其中 n 是数组的长度。每次查找范围减半,因此时间复杂度为对数级别。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

方法 2

lower_bound 函数:返回最小的满足 nums[i] >= targeti

# 方法 2:lower_bound 
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
    	return lower_bound(nums, target)

复杂度分析

  • 时间复杂度:O(log n),其中 n 是数组的长度。每次查找范围减半,因此时间复杂度为对数级别。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

方法 3

bisect_left 函数:用于在有序列表中查找插入位置。它返回一个位置,使得插入元素后列表仍然保持有序。如果元素已经存在于列表中,返回它的左侧位置。

# 方法 3:bisect_left 
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
    	return lower_bound(nums, target)

复杂度分析

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

74、搜索二维矩阵

链接:74、搜索二维矩阵

题目描述:
在这里插入图片描述

方法 1

思路:

由于矩阵的每一行是递增的,且每行的第一个数大于前一行的最后一个数,如果把矩阵每一行拼在一起,我们可以得到一个递增数组。

例如示例 1,三行拼在一起得 a=[1,3,5,7,10,11,16,20,23,30,34,60]。由于这是一个有序数组,我们可以用二分查找判断 target 是否在 matrix中。

转化方式: a[i]=matrix[⌊i/n⌋][i%n]

# 方法 1:二分法
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 检查矩阵是否为空或矩阵的第一行是否为空
        if not matrix or not matrix[0]:
            return False
        
        # 获取矩阵的行数和列数
        m, n = len(matrix), len(matrix[0])
        # 初始化左边界和右边界
        left, right = 0, m * n - 1
        
        # 开始二分查找
        while left <= right:
            # 计算中间位置,避免直接使用 (left + right) // 2 可能导致的整数溢出
            mid = left + (right - left) // 2
            # 将中间位置映射回二维矩阵中的行和列
            mid_value = matrix[mid // n][mid % n]
            
            # 如果中间元素等于目标值,返回 True
            if mid_value == target:
                return True
            # 如果中间元素小于目标值,目标值在右半部分,更新左边界
            elif mid_value < target:
                left = mid + 1
            # 如果中间元素大于目标值,目标值在左半部分,更新右边界
            else:
                right = mid - 1
        
        # 如果循环结束后仍未找到目标值,返回 False
        return False

代码解释

  1. 检查矩阵是否为空:
    • 如果矩阵为空或矩阵的第一行为空,返回 False
  2. 初始化边界:
    • m 是矩阵的行数,n 是矩阵的列数。
    • left 初始化为 0,表示一维数组的起始索引。
    • right 初始化为 m * n - 1,表示一维数组的结束索引。
  3. 循环查找:
    • 使用 while left <= right 循环,确保查找范围有效。
    • 计算中间位置 mid,并将其映射回二维矩阵中的行和列。
    • 比较中间元素 mid_value 与目标值 target
    • 如果 mid_value == target,返回 True
    • 如果 mid_value < target,目标值在右半部分,更新左边界 left = mid + 1
    • 如果 mid_value > target,目标值在左半部分,更新右边界 right = mid - 1
  4. 返回结果:
    • 如果循环结束后仍未找到目标值,返回 False

复杂度分析

  • 时间复杂度:O(log(m * n)),其中 m 是矩阵的行数,n 是矩阵的列数。每次查找范围减半,因此时间复杂度为对数级别。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

方法 2

思路:这种方法利用了矩阵的有序性,从右上角开始搜索。如果当前元素大于目标值,则向左移动;如果当前元素小于目标值,则向下移动。这样可以确保每一步都缩小搜索范围。

# 方法 2:从右上角开始的搜索
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 检查矩阵是否为空或矩阵的第一行是否为空
        if not matrix or not matrix[0]:
            return False
        
        # 获取矩阵的行数和列数
        m, n = len(matrix), len(matrix[0])
        # 从右上角开始搜索
        row, col = 0, n - 1
        
        # 当行索引在矩阵范围内且列索引在矩阵范围内时继续搜索
        while row < m and col >= 0:
            # 如果当前元素等于目标值,返回 True
            if matrix[row][col] == target:
                return True
            # 如果当前元素大于目标值,向左移动一列
            elif matrix[row][col] > target:
                col -= 1
            # 如果当前元素小于目标值,向下移动一行
            else:
                row += 1
        
        # 如果循环结束后仍未找到目标值,返回 False
        return False

代码解释

  1. 检查矩阵是否为空:
    • 如果矩阵为空或矩阵的第一行为空,返回 False
  2. 获取矩阵的行数和列数:
    • m 是矩阵的行数,n 是矩阵的列数。
  3. 初始化搜索起点:
    • 从右上角开始搜索,初始化 row 为 0,coln - 1
  4. 循环查找:
    • 使用 while row < m and col >= 0 循环,确保行索引和列索引在矩阵范围内。
    • 如果当前元素 matrix[row][col] 等于目标值,返回 True
    • 如果当前元素 matrix[row][col] 大于目标值,向左移动一列,即 col -= 1
    • 如果当前元素 matrix[row][col] 小于目标值,向下移动一行,即 row += 1
  5. 返回结果:
    • 如果循环结束后仍未找到目标值,返回 False

复杂度分析

  • 时间复杂度:O(m + n),其中 m 是矩阵的行数,n 是矩阵的列数。每一步要么向左移动一列,要么向下移动一行。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

方法 3

思路:对每一行进行二分查找,查找目标值是否在当前行中。虽然时间复杂度较高,但实现简单。

代码实现

# 方法 3: 逐行二分查找
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        for row in matrix:
            # 使用二分查找在当前行中查找目标值
            index = bisect.bisect_left(row, target)
            if index < len(row) and row[index] == target:
                return True
        return False

复杂度分析

  • 时间复杂度:O(m * log n),其中 m 是矩阵的行数,n 是矩阵的列数。对每一行进行二分查找。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

162、寻找峰值

链接:162、寻找峰值

题目描述:
在这里插入图片描述

方法 1

思路:由于左右元素不相同,因此nums的最大值不会在一起,因此找到最大值的index就一定是峰值。

# 方法 1:最大值
class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        return nums.index(max(nums))

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

方法 2

在这里插入图片描述
作者:灵茶山艾府

# 方法 2:二分法
class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        # 初始化左右边界,使用开区间 (-1, len(nums) - 1)
        left, right = -1, len(nums) - 1
        
        # 当开区间不为空时继续搜索
        while left + 1 < right:
            # 计算中间位置
            mid = (left + right) // 2
            
            # 如果中间元素大于其右侧元素,则峰值在左侧(包括mid)
            if nums[mid] > nums[mid + 1]:
                right = mid
            # 否则,峰值在右侧
            else:
                left = mid
        
        # 返回右边界,即峰值元素的索引
        return right

# 作者:灵茶山艾府
# 链接:https://leetcode.cn/problems/find-peak-element/solutions/1987497/by-endlesscheng-9ass/
# 来源:力扣(LeetCode)

复杂度分析

  • 时间复杂度:O(log n),其中 n 是数组的长度。
  • 空间复杂度:O(1)。

33、搜索旋转排序数组

链接:33、搜索旋转排序数组

题目描述:
在这里插入图片描述

方法 1

思考过程:

  1. 初始化左右边界:
    • 初始化 left 为数组的起始索引 0,right 为数组的结束索引 len(nums) - 1
  2. 二分查找:
    • 计算中间位置 mid
    • 判断 nums[mid] 是否等于目标值 target,如果是则返回 mid
    • 如果 nums[mid] 不等于目标值,则需要判断目标值在左半部分还是右半部分。
      • 如果 nums[left] <= nums[mid],说明左半部分是有序的。
        • 如果 targetnums[left]nums[mid] 之间,则在左半部分继续查找,否则在右半部分继续查找。
      • 如果 nums[left] > nums[mid],说明右半部分是有序的。
        • 如果 targetnums[mid]nums[right] 之间,则在右半部分继续查找,否则在左半部分继续查找。
  3. 返回结果:
    • 如果在整个查找过程中没有找到目标值,则返回 -1
# 方法 1:二分类
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = (left + right) // 2
            
            if nums[mid] == target:
                return mid
            
            # 判断左半部分是否有序
            if nums[left] <= nums[mid]:
                # 判断目标值是否在左半部分
                if nums[left] <= target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                # 判断目标值是否在右半部分
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        
        return -1

代码解释

  1. 初始化边界:
    • left 初始化为 0,表示数组的起始索引。
    • right 初始化为 len(nums) - 1,表示数组的结束索引。
  2. 循环查找:
    • 使用 while left <= right 循环,确保查找范围有效。
    • 计算中间位置 mid,使用公式 mid = (left + right) // 2
    • 如果 nums[mid] 等于目标值 target,则返回 mid
    • 判断左半部分是否有序:
      • 如果 nums[left] <= nums[mid],说明左半部分是有序的。
        • 如果目标值 targetnums[left]nums[mid] 之间,则在左半部分继续查找,更新右边界 right = mid - 1
        • 否则,在右半部分继续查找,更新左边界 left = mid + 1
      • 如果 nums[left] > nums[mid],说明右半部分是有序的。
        • 如果目标值 targetnums[mid]nums[right] 之间,则在右半部分继续查找,更新左边界 left = mid + 1
        • 否则,在左半部分继续查找,更新右边界 right = mid - 1
  3. 返回结果:
    • 如果在整个查找过程中没有找到目标值,则返回 -1

复杂度分析

  • 时间复杂度:O(log n),其中 n 是数组的长度。二分查找每次将搜索范围减半。
  • 空间复杂度:O(1)

方法 2

这种方法是最直接的,通过遍历整个数组来查找目标值。虽然简单,但时间复杂度为 O(n),不符合题目要求。

# 方法 2:线性扫描法
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        for i in range(len(nums)):
            if nums[i] == target:
                return i
        return -1

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值