leetcode刷题笔记——二分查找

目前完成的贪心相关的leetcode算法题序号:
中等:80,81
困难:4

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

文章目录

    • leetcode刷题笔记——二分查找
  • 算法理解
  • 一、33题:搜索选择排序数组
    • 1.题干
    • 2.思路
    • 3.代码
    • 4. 问题点思考
  • 二、81题:搜索旋转排序数组II
    • 1.题干
    • 2.解题思路
    • 3.代码
    • 4. 注意点
  • 三、4题:寻找两个正序数组的中位数
    • 1. 题干
    • 2. 思路
    • 3. 代码
    • 四、牛客网——牛牛打气球
  • 1. 题干
    • 2. 解题思路
    • 3. 代码

算法理解

二分查找也常被称为二分法或者折半查找,每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为n的数组,二分查找的时间复杂度为O(log n)。

具体到代码上,二分查找时区间的左右端取开区间还是闭区间在绝大多数时候都可以,因此有些初学者会容易搞不清楚如何定义区间开闭性
这里提供两个小诀窍:
1)第一是尝试熟练使用一种写法,比如左闭右开(满足C++、Python 等语言的习惯)或左闭右闭(便于处理边界条件),尽量只保持这一种写法;
2)第二是在刷题时思考如果最后区间只剩下一个数或者两个数,自己的写法是否会陷入死循环,如果某种写法无法跳出死循环,则考虑尝试另一种写法。

二分查找也可以看作双指针的一种特殊情况,但我们一般会将二者区分。双指针类型的题,指针通常是一步一步移动的,而在二分查找里,指针每次移动半个区间长度。


一、33题:搜索选择排序数组

1.题干

升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。

请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

说明:
nums 中的每个值都 独一无二
nums 肯定会在某个点上旋转

2.思路

本题给出条件,nums中不存在重复值。

数组经过旋转,说明数组是两段升序数组构成的,因此每次进行二分时,必然有一半是有序数组。
如此一来,就可以通过寻找有序数组,并判断二分的点对应的元素值是否在有序数组中,如果在,将区间更新到有序数组的范围中,如果不在,则将区间更新到另一半的范围中。

解题思路:
1)将数组进行二分,寻找有序数组(认为右区间>=左区间即可能为有序数组);
2)对两部分分别进行判断,如果可能是有序数组,就判断目标值是否在区间中;
3)如果在(由于没有重复值),则将区间更新为该数组的范围,如果不在更新到另一半数组区间;

3.代码

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums)-1#初始化区间为数组的整个区间while right >= left:#注意二分法的循环终止条件,需要与循环和的判断条件进行配合mid = int((left + right) / 2)#如果找到目标值,则返回索引if nums[mid] == target:return mid#如果二分后的左半区间可能有序,则判断目标值是否在区间内,在则将右边界更新为mid-1#不在则说明目标值如果存在数组,也只能在右半区间,将左边界更新为mid+1if nums[left] <= nums[mid]:if nums[left] <= target < nums[mid]:right = mid - 1else:left = mid + 1#右半区间的处理逻辑与左半区间对称elif nums[right] >= nums[mid]:if nums[mid] < target <= nums[right]:left = mid+1else:right = mid - 1#二分循环结束的情况有2种:#1)找到了目标值,这是循环直接返回索引结果,不需要后续处理;#2)right < left,二分法最终也没能够找到目标值,需要进行判断,返回-1if right < left:#right < left还能够同时覆盖数组长度为0的情况return -1return None

4. 问题点思考

二分法的问题往往在于区间的边界设置和判断条件的匹配上,配置的不好往往会漏解,甚至陷入死循环。
采取的策略:
1)建立一套自己习惯的边界设置方法和判断条件,熟悉这套设置方法下的特性;
2)根据实际情况在此基础上进行调整;

二、81题:搜索旋转排序数组II

1.题干

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

数组中可能包含重复元素

2.解题思路

这题是33题的进阶问题,注意增加的问题点就是,数组中可能存在重复元素。

33题解法在数组中出现重复元素情况下,会遇到一个问题:
如果重复元素刚好出现在二分之后,二分点的元素与左边界/右边界的元素重复,则就不能够有效判断出两半部分是否为有序数组

针对这个问题对33题的解法进行改进:
1)二分后,判断完二分点的元素不是目标值之后,对当前区间的左边界和右边界进行检测;
2)如果该元素与二分点的元素是重复值,则将重复值去除;
3)直到左右边界的值不等于二分点的元素值,或者左右边界的索引达到了二分点的位置;

3.代码

class Solution:def search(self, nums: List[int], target: int) -> bool:left, right = 0, len(nums) - 1while right >= left:mid = int((left + right) / 2)if nums[mid] == target:return True#33题算法的改进:#    1)如果检测到左边界与二分点的元素值重复,左边界右移,直到左边界取值与二分点不等,或者左边界到达二分点位置#  2)如果检测到右边界与二分点的元素值重复,右边界左移,直到右边界取值与二分点不等,或者右边界到达二分点位置while nums[mid] == nums[left] and left < mid:left += 1while nums[mid] == nums[right] and mid < right:right -= 1if nums[left] <= nums[mid]:if nums[left] <= target < nums[mid]:right = mid - 1else:left = mid + 1elif nums[mid] <= nums[right]:if nums[mid] < target <= nums[right]:left = mid + 1else:right = mid - 1if right < left:return False

4. 注意点

三、4题:寻找两个正序数组的中位数

1. 题干

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

进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

2. 思路

对两个有序数组寻找中位数,即为查找两个数组的联合分割点,使得分割线两边的元素数量一致:
以长度较小的数组为基准进行二分:
1)由于两个数组的长度已知,因此知道了某个数组的分割点之后,另一个数组的分割点也就能得到;
2)以长度较小的数组为基准,进行二分法划分数组,之后就能确定长度较长的数组的分割点,判断两个数组在分割点处是否满足交叉小于等于条件,如果不满足,做相应调整(左边最大值大于右边最小值,则小数组的分割点需要左移),直至找到正确的分割点;
3)找到分割点之后,还需要根据分割点的位置,考虑几种特殊情况下的处理方法;

3. 代码

class Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:#将较小的数组作为nums1,以nums1为基准进行二分法if len(nums1) > len(nums2):nums1, nums2 = nums2, nums1m = len(nums1)n = len(nums2)#记录分割点左边需要包含的元素数量:#如果两个数组的总长度为奇数,则分割线左边要比右边多一个元素,最终的中位数即为分割后左边的最大值#如果两个数组的总长度为偶数,则分割线两边的元素数量相等,最终的中位数为左边最大值和右边最小值的均值totalLen = (m + n + 1) // 2#left和right表示的是小数组在分割线左右两边的元素数量left, right = 0, len(nums1)#分割线位置需要满足的条件:#左边的最大值 <= 右边的最小值,即nums1[i-1] <= nums2[j]或者nums1[i] >= nums2[j-1]#当left == right时,即为找到了满足条件的分割点while left < right:i = left + (right - left + 1) // 2j = totalLen - iif nums1[i-1] > nums2[j]:right = i - 1else:left = ii = leftj = totalLen - i#如果两个数组的分割线中,有一个左边没有元素(不会同时为0),则分割后的整体的左边最大值就是另一个数组的左边的最大值if i == 0:leftmax = nums2[j-1]elif j == 0:leftmax = nums1[i-1]else:leftmax = max(nums1[i-1], nums2[j-1])#如果两个数组的分割线中,有一个右边没有元素(有可能出现右边都没有元素的情况,数组长度为0和1时)#因此这里需要特殊处理一下,利用mid值规避划分后右边都没有元素的情况mid = int(-1*(m+n-1)//2)if i == len(nums1):rightmin = nums2[mid]elif j == len(nums2):rightmin = nums1[mid]else:rightmin = min(nums1[i], nums2[j])#判断数组总长的奇偶,为奇数则中位数是分割线左边的最大值,偶数则为分割线左边最大值和右边最小值的平均值if (m + n) % 2 == 1:return float(leftmax)else:return (leftmax + rightmin) / 2

二分法的难点一致都是细节,如何确定循环条件,如何确定索引计算方法以防止超出列表的索引范围等等

四、牛客网——牛牛打气球

1. 题干

有n个气球,每个气球都有一个坚韧度,牛牛有一把全屏武器,可以使每一个气球的坚韧度都下降b(坚韧度不会为负数),特别的:每次释放武器的时候,牛牛可以选择一个气球,使得这个气球多承受a点伤害。

牛牛想知道,最少释放几次武器,可以使得所有气球的坚韧度都变成0呢?

2. 解题思路

设定一个足够大的武器释放次数的上下边界,二分判断该范围内,各个数是否能够满足条件,直到范围压缩到1,即得结果。

3. 代码

import math
def check(n, nums, a, b):rest = 0weaken = n * bfor val in nums:#注意:这里需要,每次二分法遍历的数,经过基础全屏攻击之后,打掉各个气球所需要的剩余次数(额外打击次数)#不能简单求和后,再与n*a比较,因为这样计算得到的就不是打掉各个气球所需要的剩余次数之和#可能出现不同气球剩余次数互相补充的情况,一定要严谨rest += math.ceil(max(0, val - weaken) / a)return rest <= nif __name__ == "__main__":n, a, b = [int(x) for x in input().strip().split()]nums = [int(x) for x in input().strip().split()]max_n = max(nums)low, high = 1, 10 ** 9while low < high:mid = low + (high - low) // 2if check(mid, nums, a, b):high = midelse:low = mid + 1print(low)

leetcode刷题笔记——二分查找相关推荐

  1. LeetCode刷题笔记 二分查找 局部有序

    二分查找的局部有序情况 ​ 我们已经知道二分查找是一种在有序数组中查找某一特定元素的查找算法. ​ 那如果一个数组不是整体有序,而是局部有序呢?这时我们就可以通过分治策略,我们在局部有序的区间内进行二 ...

  2. 卷进大厂系列之LeetCode刷题笔记:二分查找(简单)

    LeetCode刷题笔记:二分查找(简单) 学算法,刷力扣,加油卷,进大厂! 题目描述 涉及算法 题目解答 学算法,刷力扣,加油卷,进大厂! 题目描述 力扣题目链接 给定一个 n 个元素有序的(升序) ...

  3. LeetCode刷题笔记汇总

    LeetCode刷题笔记汇总 第一次刷LeetCode写的一些笔记. 1.两数之和 3.无重复字符的最长子串 15.三数之和 18.四数之和 19.删除链表的倒数第 N 个结点 20.有效的括号 21 ...

  4. LeetCode刷题笔记2——数组2

    LeetCode刷题笔记2--数组2 重塑数组 题目 在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原 ...

  5. 小何同学的leetcode刷题笔记 基础篇(01)整数反转

    小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...

  6. 【leetcode刷题笔记】动态规划

    #[leetcode刷题笔记]动态规划 石子游戏 public boolean stoneGame(int[] piles) {int N = piles.length;// dp[i][j] is ...

  7. LeetCode刷题笔记-动态规划-day4

    文章目录 LeetCode刷题笔记-动态规划-day4 55. 跳跃游戏 1.题目 2.解题思路 3.代码 45. 跳跃游戏 II 1.题目 2.解题思路 3.代码 LeetCode刷题笔记-动态规划 ...

  8. LeetCode刷题笔记- 15.三数之和

    LeetCode刷题笔记- 15.三数之和 C语言 题目 注意点 C语言 /*** Return an array of arrays of size *returnSize.* The sizes ...

  9. LeetCode刷题笔记第6题:Z字形变换

    LeetCode刷题笔记第6题:Z字形变换 想法: 要完成字符串根据给定的行数从上往下,从左到右完成Z字形排列.当只有一行时直接返回原字符串,当行数大于1时,先以行数构建一个行数数值个空字符串的列表, ...

最新文章

  1. 30道linux面试题与答案(21-30)
  2. 中国科学家发现恐惧情绪的新环路
  3. 基于Java的全文索引
  4. ASP.NET Core微服务(七)——【docker部署linux上线】(RDS+API接口测试部分)
  5. VSS SVN GIT SVN 加锁签出
  6. cursoradpter自动更新
  7. vue中页面跳转,显示在顶部
  8. 计算机网络学习笔记(3. 计算机网络结构)
  9. 改变风格(css)的四种方法
  10. android逆向学习路线(适合新手)
  11. 神经网络的原理和应用,神经网络理论及应用
  12. 面经_黑盒测试与白盒测试
  13. 单片机内部FLASH的字节操作
  14. 关于fiddler和charles手机代理以后无法上网的问题解决
  15. 管螺纹如何标注_你所不知道的机械螺纹全面常识(分享篇),赶紧收藏下吧
  16. python 收银系统_实现全栈收银系统(Node+Vue)(一)
  17. 深入浅出计算机组成原理04-穿越功耗墙,我们该从哪些方面提升“性能”?
  18. 华为荣耀20s云服务_华为荣耀Honor特色应用,云服务Cloud+使用教程
  19. java图形界面编程如何给窗口设置背景图片
  20. Anaconda中conda命令或者pip命令使用豆瓣镜像下载包

热门文章

  1. [ VRTK ] --- 学习日记01
  2. 在校大学生谈项目管理
  3. Curl安装测试http3——openssl+nghttp3+ngtcp2+curl
  4. 中国四大最年轻的亿万富翁
  5. PaaS云平台技术架构及发展趋势研讨会 暨中服软件战略融资发布会成功召开
  6. 记一次服务器CPU飙高使用率超过100%原因分析(Arthas)
  7. 用tensorflow实现ASSP层
  8. 苹果5概念机_苹果iPhone 12 Pro最新概念图曝光:后置“浴霸”四摄,LiDAR加持!...
  9. 数字签名技术以及RSA算法的原理实现
  10. 使用ImageMagick