算法学习|二分查找

Posted java技术大本营

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法学习|二分查找相关的知识,希望对你有一定的参考价值。

二分查找

  1. 二分查找的也称为折半查找,由于每次都能够将查找区间缩小为原来一半,这种算法的时间复杂度为O(logN)。

  2. 计算中值的方法有两种
    m = (low+ hight) / 2
    m = low + (hight - low) / 2
    推荐使用第二种,因为减法不会涉及到数据因为相加导致的溢出问题。

  3. 查找的返回值一般返回为low,但是实际情况需要从实际的题目出发。

  4. 可以多举例子,便于理解边界问题。

Question 1 

x 的平方根(https://leetcode-cn.com/problems/sqrtx/)(长按复制到浏览器即可到leetcode对应的题目

算法的逻辑在具体的代码注释里

/** * 计算并返回 x 的平方根,其中 x 是非负整数。 * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 */public class Question1 { public int mySqrt(int x) { if (x <= 1) { return x; }// 二分 int low = 1; int hight = x;// 结合双指针 while (low <= hight) {// 定义中间值 这样定义middle不会出现溢出问题 int middle = low + (hight - low) / 2;// 计算是否能够直接算出开方 int sqrt = x / middle; if (sqrt == middle) { return middle; } else if (middle > sqrt) {// 大了 调整hight hight = middle - 1; } else {// 小了 调整low low = middle + 1; } } return hight; }}

Question 2 

寻找比目标字母大的最小字母https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/)

/** * 给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母。 * 数组里字母的顺序是循环的。举个例子,如果目标字母target = 'z' 并且有序数组为 letters = ['a', 'b'],则答案返回 'a'。 * 关键词:有序、循环 */public class Question2 { public char nextGreatestLetter(char[] letters, char target) {// 定义头尾 int hight = letters.length-1; int low = 0; while (hight>=low){ int middle = low + (hight-low)/2; if(target>=letters[middle]){// middle小了 low = middle+1; }else {// middle大了 hight = middle-1; } }// 最终得到的low是接近结果的(因为只有low大于hight的时候才会跳出循环) return low<letters.length?letters[low]:letters[0]; }}

Question3

 有序数组中的单一元素(https://leetcode-cn.com/problems/single-element-in-a-sorted-array/)

/** * 给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。 * 注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。 * <p> * 因为要求时间复杂度和空间复杂度,所以不能够使用直接遍历的方法。 */public class Question3 { public int singleNonDuplicate(int[] nums) { int low = 0; int hight = nums.length - 1; while (hight > low) { int middle = low + (hight - low) / 2;// 判断中间的数是偶数还是奇数 if (middle % 2 == 0) {// 偶数 说明middle前面有偶数个数字 此时判断middle 和 middle+1的关系 if (nums[middle] == nums[middle + 1]) {// 说明middle前面数字都是出现两次的 low = middle + 2; } else {// 说明middle后面的数字都是正确的 hight = middle; } } else {// 奇数 说明前后都只有奇数个数字 判断middle 和middle+1的关系 if (nums[middle] == nums[middle + 1]) {// 说明middle之后的都是正确的 hight = middle - 1; } else {// 说明middle+1之后的是有问题的 low = middle + 1; } } } return nums[low]; }}

Question4

第一个错误的版本(https://leetcode-cn.com/problems/first-bad-version/)

/** * 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。 * 由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 * <p> * 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 * <p> * 你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。 * 实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 */public class Question4 { public int firstBadVersion(int n) { int low = 1; int hight = n; while (hight > low) { int middle = low + (hight - low) / 2;// 二分查找 提高效率 if(isBadVersion(middle)){// 版本出现错误 可能是在之前就出错了,所以需要往前回溯 hight = middle; }else {// 版本正确 往后推进 low = middle+1; } } return low; }
/** * leetcode系统提供的方法 * * @param version * @return true:错误版本 * false:正确版本 */// boolean isBadVersion(int version) {// return false;// }}

Question 5 

寻找旋转排序数组中的最小值(https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/)

/** * 旋转数组的最小数字问题 * 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 * ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 * 请找出其中最小的元素。 * 你可以假设数组中不存在重复元素。 */public class Question5 { public int findMin(int[] nums) { int low = 0; int hight = nums.length - 1; while (hight > low) { int middle = low + (hight - low) / 2;// 旋转数组的特征就是:发生旋转的话,就会有一个旋转点// 我们的目标就是找到这个旋转点// (通过二分法比对middle和hight的值)// hight>middle的话 说明后半部分没哟旋转点 反之同理! if(nums[middle]<nums[hight]){// 说明后半部分是正常的顺序的数组,没有旋转点,将目标缩小到前半部分 hight = middle; }else {// 说明后半部分数组有旋转点,继续调查后半部分的数组 low = middle+1; } } return nums[low];    }

Question 6 

在排序数组中查找元素的第一个和最后一个位置(https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)

/** * 题目描述: * 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 * 你的算法时间复杂度必须是 O(log n) 级别。 * 如果数组中不存在目标值,返回 [-1, -1]。 * <p> * 关键词:升序数组、时间复杂度要求O(log n) 所以需要使用二分查找算法 */public class Question6 { public int[] searchRange(int[] nums, int target) {// 寻找第一次出现target的地方 int first = binarySearch(nums, target);// 寻找第一次出现比target大1的数字的地方 int last = binarySearch(nums, target + 1) - 1; if (first == nums.length || nums[first] != target) { return new int[]{-1, -1}; } else { return new int[]{first, Math.max(first, last)}; } }
/** * 封装成方法 用于寻找target第一次出现的位置 * @param nums * @param target * @return */ private int binarySearch(int[] nums, int target) { int low = 0; int hight = nums.length; while (hight > low) { int middle = low + (hight - low) / 2; if (nums[middle] >= target) {// 说明目标在上半部分 hight = middle; } else {// 说明目标在下半部分 low = middle + 1; } } return low; }}


以上是关于算法学习|二分查找的主要内容,如果未能解决你的问题,请参考以下文章

二分查找算法(下):通过 LeetCode 周赛学习二分查找算法

算法学习系列 -- 二分查找

算法学习系列 -- 二分查找

算法学习|二分查找

学习数据结构笔记 ---查找算法(线性查找,二分查找,插值查找,斐波那契查找)

谈谈我是怎么学习二分查找的(学习算法没有捷径)