LeetCode-题目详解(三):二分查找法【 O(logn)】

704-二分查找

给定一个 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]之间。

二分查找是一种基于比较目标值和数组中间元素的教科书式算法。

  • 如果目标值等于中间元素,则找到目标值。
  • 如果目标值较小,继续在左侧搜索。
  • 如果目标值较大,则继续在右侧搜索。
class Solution {
   
public:
    int search(vector<int>& nums, int target) {
   
        int left = 0;
        int right = nums.size() - 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:
        left, right = 0, len(nums) - 1
        while left <= right:
            pivot = left + (right - left) // 2
            if nums[pivot] == target:
                return pivot
            if target < nums[pivot]:
                right = pivot - 1
            else:
                left = pivot + 1
        return -1

4-寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5:

输入:nums1 = [2], nums2 = []
输出:2.00000

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

方法一:二分查找【时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def getKthElement(k):
            """
            - 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
            - 这里的 "/" 表示整除
            - nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
            - nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
            - 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
            - 这样 pivot 本身最大也只能是第 k-1 小的元素
            - 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
            - 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
            - 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
            """
            
            index1, index2 = 0, 0
            while True:
                # 特殊情况
                if index1 == m:
                    return nums2[index2 + k - 1]
                if index2 == n:
                    return nums1[index1 + k - 1]
                if k == 1:
                    return min(nums1[index1], nums2[index2])

                # 正常情况
                newIndex1 = min(index1 + k // 2 - 1, m - 1)
                newIndex2 = min(index2 + k // 2 - 1, n - 1)
                pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2]
                if pivot1 <= pivot2:
                    k -= newIndex1 - index1 + 1
                    index1 = newIndex1 + 1
                else:
                    k -= newIndex2 - index2 + 1
                    index2 = newIndex2 + 1
        
        m, n = len(nums1), len(nums2)
        totalLength = m + n
        if totalLength % 2 == 1:
            return getKthElement((totalLength + 1) // 2)
        else:
            return (getKthElement(totalLength // 2) + getKthElement(totalLength // 2 + 1)) / 2

方法二:
一般看到O(log())级别的,就先想二分,分而治之的那些思想。比如归并排序,快排……

本题主要处理好边界问题,相对于思想,边界处理和代码技巧更重要

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        n1, n2 = len(nums1), len(nums2)

        def get_kth_element(k: int) -> int:
            i1, i2 = 0, 0
            while k != 0:
                if i1 == n1:
                    return nums2[i2 + k - 1]
                if i2 == n2:
                    return nums1[i1 + k - 1]
                if k == 1:                              #1//2 = 0 所有也要判断一下
                    return min(nums1[i1], nums2[i2])

                new_i1 = min(i1 + k//2 - 1, n1 - 1)                  #每个数组贡献 k//2
                new_i2 = min(i2 + k//2 - 1, n2 - 1)
                pivot_1, pivot_2 = nums1[new_i1], nums2[new_i2]
                if pivot_1 <= pivot_2:                  #把小的那段扔掉
                    k -= (new_i1 - i1 + 1)              #做好index的更新
                    i1 = new_i1 + 1
                else:
                    k -= (new_i2 - i2 + 1)
                    i2 = new_i2 + 1

        n = n1 + n2
        if n % 2 == 1:
            return get_kth_element( (n+1) // 2 )    # 0 1 2 3 4  n=5 取第3个
        else:
            return ( get_kth_element( n//2 ) + get_kth_element( (n+2)//2 ) ) / 2.0  # 0 1 2 3 n=4 取第2个,第3个的aver

方法三:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/dong-yong-er-fen-cha-zhao-mo-ban-lai-qiao-miao-jie/

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:

        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1
        len1, len2 = len(nums1), len(nums2)
        
        left, right, half_len = 0, len1, (len1 + len2 + 1) // 2
        mid1 = (left + right) // 2
        mid2 = half_len - mid1
        
        while left < right:
            if mid1 < len1 and nums2[mid2-1] > nums1[mid1]:
                left = mid1 + 1
            else:
                right = mid1
            mid1 = (left + right) // 2
            mid2 = half_len - mid1
        
        if mid1 == 0: 
            max_of_left = nums2[mid2-1]
        elif mid2 == 0: 
            max_of_left = nums1[mid1-1]
        else: 
            max_of_left = max(nums1[mid1-1], nums2[mid2-1])

        if (len1 + len2) % 2 == 1:
            return max_of_left

        if mid1 == len1: 
            min_of_right = nums2[mid2]
        elif mid2 == len2: 
            min_of_right = nums1[mid1]
        else: 
            min_of_right = min(nums1[mid1], nums2[mid2])

        return (max_of_left + min_of_right) / 2

方法四:

  1. 将两个列表整合在一起
  2. 对新列表进行排序
  3. 判断列表长度,如果长度是奇数,取中间数值,如果是偶数,取中间两个数值平均数
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        list_new = nums1 + nums2
        list_new.sort()
        if len(list_new) % 2 == 0:
            return (list_new[len(list_new)//2]+list_new[len(list_new)//2 - 1])/2
        else:
            return  list_new[len(list_new)//2]

方法五:

class Solution:
    #这题很自然地想到归并排序,再取中间数,但是是nlogn的复杂度,题目要求logn
    #所以要用二分法来巧妙地进一步降低时间复杂度
    #思想就是利用总体中位数的性质和左右中位数之间的关系来把所有的数先分成两堆,然后再在两堆的边界返回答案
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m = len(nums1)
        n = len(nums2)
        # 让nums2成为更长的那一个数组
        if m>n:
            nums1,nums2,m,n = nums2,nums1,n,m
        
        # 如果两个都为空的异常处理
        if n == 0:
            raise ValueError

        # nums1中index在lmid左边的都被分到左堆,nums2中rmid左边的都被分到左堆
        left,right = 0,m
        
        # 二分答案
        while(left<=right):
            lmid = left + (right-left)//2
            # 左堆最大的只有可能是nums1[lmid-1],nums2[rmid-1]
            # 右堆最小只有可能是nums1[lmid],nums2[rmid]
            # 让左右堆大致相等需要满足的条件是lmid+rmid = m-lmid+n-rmid 即 rmid = (m+n-2lmid)//2
            # 为什么是大致呢?因为有总数为奇数的情况,这里用向下取整数操作,所以如果是奇数,右堆会多1
            rmid = (m+n-2*lmid)//2 
            
            # 前面的判断条件只是为了保证不会index out of range
            if(lmid>0 and nums1[lmid-1] > nums2[rmid]):
                # lmid太大了,这是里精确查找,不是左闭右开,而是双闭区间,所以直接移动一位
                right = lmid-1
            elif(lmid<m and nums2[rmid-1] > nums1[lmid]):
                left = lmid+1
            # 满足条件
            else:
                # 边界情况处理,都是为了不out of index
                # 依次得到左堆最大和右堆最小
                if(lmid == m): 
                    minright = nums2[rmid]
                elif(rmid == n): 
                    minright = nums1[lmid]
                else:
                    minright = min(nums1[lmid],nums2[rmid])        
                    
                if(lmid == 0): 
                    maxleft = nums2[rmid-1]
                elif(rmid == 0): 
                    maxleft = nums1[lmid-1]
                else:
                    maxleft = max(nums1[lmid-1],nums2[rmid-1])
                
                # 前面也提过,因为取中间的时候用的是向下取整,所以如果总数是奇数的话,
                # 应该是右边个数多一些,边界的minright就是中位数
                if((m+n)%2) == 1:
                    return minright 
     
                # 否则我们在两个值中间做个平均
                return (maxleft + minright)/2

300-最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 104<=nums[i]<=104

进阶:

  • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode-soluti/

方法一:动态规划【 O ( n 2 ) O(n^2) O(n2),其中 n 为数组 nums 的长度】

在这里插入图片描述

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = []
        for i in range(len(nums)):
            dp.append(1)
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

方法二:贪心 + 二分查找【 O ( n l o g n ) O(nlogn) O(nlogn),其中 n 为数组 nums 的长度】

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        d = []
        for n in nums:
            if not d or n > d[-1]:
                d.append(n)
            else:
                l, r = 0, len(d) - 1
                loc = r
                while l <= r:
                    mid = (l + r) // 2
                    if d[mid] >= n:
                        loc = mid
                        r = mid - 1
                    else:
                        l = mid + 1
                d[loc] = n
        return len(d)

69-x 的平方根

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:

输入: 4
输出: 2

示例 2:

输入: 8
输出: 2
说明: 8 的平方根是 2.82842..., 
     由于返回类型是整数,小数部分将被舍去。

本题是一道常见的面试题,面试官一般会要求面试者在不使用 x \sqrt{x} x 函数的情况下,得到 x x x 的平方根的整数部分。一般的思路会有以下几种:

  • 通过其它的数学函数代替平方根函数得到精确结果,取整数部分作为答案;
  • 通过数学方法得到近似结果,直接作为答案。

https://leetcode-cn.com/problems/sqrtx/solution/x-de-ping-fang-gen-by-leetcode-solution/

方法一:袖珍计算器算法【 O ( 1 ) O(1) O(1)
在这里插入图片描述

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return 0
        ans = int(math.exp(0.5 * math.log(x)))
        return ans + 1 if (ans + 1) ** 2 <= x else ans

方法二:二分查找【时间复杂度: O ( l o g x ) O(logx) O(logx),即为二分查找需要的次数。】

class Solution {
   
public:
    int mySqrt(int x) {
   
        int left = 0;
        int right = x;
        int result = -1;

        while(left <= right){
   
            long mid = left + (right - left) / 2;
            if(mid * mid == x){
   
                return mid;
            }else if(mid * mid < x){
   
                result = mid;
                left = mid + 1;
            }else{
   
                right = mid - 1;
            }
        }

        return result;
    }
};
class Solution:
    def mySqrt(self, x: int) -> int:
        left, right = 0, x
        result = -1
        
        while left <= right:
            mid = left + (right - left) // 2
            if mid*mid == x:
                return mid
            elif mid*mid < x:
                left = mid + 1
                result = mid
            else:
                right = mid - 1

        return result
class Solution:
    def mySqrt(self, x: int) -> int:
        l, r, ans = 0, x, -1
        while l <= r:
            mid = (l + r) // 2
            if mid * mid <= x:
                ans = mid
                l = mid + 1
            else:
                r = mid - 1
        return ans

74-搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

示例 1:
在这里插入图片描述

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:
在这里插入图片描述

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 100
  • − 1 0 4 < = m a t r i x [ i ] [ j ] , t a r g e t < = 1 0 4 -10^4 <= matrix[i][j], target <= 10^4 104<=matrix[i][j],target<=104

凡是能通过暴力AC的题都不能算中等题…这道题分别用暴力、贪心、二分完成

解题1.双层for循环:

class Solution:
    def searchMatrix(self, matrix, target):
        for i in matrix:
            for j in i:
                
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值