当前位置:   article > 正文

二分查找算法详解_二分搜寻法

二分搜寻法

1.介绍

二分查找,也称折半查找(Binary Search),它是一种效率较高的查找方法,实现原理简单,但细节相对复杂的算法。关于二分查找,有个经典的理解,思路很简单,细节是魔鬼 。 二分查找的常用场景一般包括:寻找一个数、寻找左侧边界、寻找右侧边界。而细节,主要体现在,while循环中用 < 还是 <=mid是否应该加1等。 下面从常用场景,结合 leetcode 题目,简单介绍下。

2. 寻找一个数

这是最简单的二分查找应用场景,即从有序数组中搜索一个目标数。

题目描述

leetcode 704.二分查找

给定一个n个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例:

输入: nums = [-1,0,3,5,9,12], target = 9

输出: 4

解释: 9 出现在 nums 中并且下标为 4

题目解析

这个题目即是典型的二分查找,为有序数组中,查找目标值,我们定义 mid 为数组中点,即是比较 nums[mid] 与 tagert 大小:

如果 nums[mid] = target,则下标 mid 即为要寻找的下标;

如果 nums[mid] > target,则 target 只可能在下标 mid 的左侧;

如果 nums[mid] < target,则 target 只可能在下标 mid 的右侧。

代码实现

  1. int search(int* nums, int numsSize, int target) {
  2. int l = 0;
  3. int r = numsSize - 1;
  4. while (l <= right) {
  5. int mid = l + (r - l) / 2;
  6. if (nums[mid] == target)
  7. return mid; // 找到目标值,返回下标
  8. else if (nums[mid] < target)
  9. l = mid + 1;
  10. else if (nums[mid] > target)
  11. r = mid - 1;
  12. }
  13. return -1; //目标值不存在,返回-1
  14. }

现在关于细节问题,做下分析理解:

2.1 为什么while循环里要用 <=

我们注意到,在初始化时,left 值为 0,而 right 值为 numsSize - 1,即最后一个元素的索引为 numsSize - 1,相当于我们的搜索区间为 [left, right],两端都闭的区间。如果 right初值为 numsSize时,while 循环语句里需要用 <作为条件判断。

2.2 为什么 left = mid + 1 和 right = mid - 1

如上文描述,我们的搜索区间为 [left, right],以mid为中点,逐步缩小查找范围,如果在索引 mid里没有找到 target,接下来就要搜索 [left, mid - 1] 和 [mid + 1, right] 区间范围内是否有目标值。

3.寻找左侧边界

题目描述<一>

我们对上面 leetcode 704题目做下修改,在有序数组中,存在元素相等的情况,找到数组中和target相等的 最左侧元素下标

示例:

输入: nums = [-1,0,3,5,9,9,12], target = 9

输出: 4

解释: 9 出现在 nums 中 最左侧下标为 4

题目解析 结合上面寻找一个数思路分析,在比较 nums[mid] 与 tagert 大小时,如果 nums[mid] = target,下标 mid 即为要寻找的下标,此时不返回,收缩右侧边界接着找下一个r = mid - 1,其他情况和寻找一个数实现相同。

代码实现

  1. int search(int* nums, int numsSize, int target) {
  2. int l = 0;
  3. int r = numsSize - 1;
  4. while (l <= right) {
  5. int mid = l + (r - l) / 2;
  6. if (nums[mid] == target)
  7. r = mid - 1; // 找到目标值,收缩右侧边界
  8. else if (nums[mid] < target)
  9. l = mid + 1;
  10. else if (nums[mid] > target)
  11. r = mid - 1;
  12. }
  13. if (l < 0 || nums[l] != target)
  14. return -1;
  15. return l;
  16. }

题目描述<二>

153. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]

若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

题目解析

l 和 r 指针不断向中间移动,进行二分,则有2种情况:

当前节点 比 r 节点的值小, 即 nums[mid] < nums[r],则 r 等于 mid

当前节点大于等于 r 节点的时候,即 nums[mid] >= nums[r],则让 l 等于 mid + 1

代码实现

  1. int findMin(int* nums, int numsSize){
  2. int l = 0;
  3. int r = numsSize - 1;
  4. while (l < r) {
  5. int mid = l + (r - l) / 2;
  6. if (nums[mid] < nums[r])
  7. r = mid;
  8. else
  9. l = mid + 1;
  10. }
  11. return nums[l];
  12. }

4.寻找右侧边界

我们对上面 leetcode 704题目做下修改,在有序数组中,存在元素相等的情况,找到数组中和target相等的 最右侧元素下标

示例:

输入: nums = [-1,0,3,5,9,9,12], target = 9

输出: 5

解释: 9 出现在 nums 中最右侧下标为 5

题目解析 结合上面寻找一个数思路分析,在比较 nums[mid] 与 tagert 大小时,如果 nums[mid] = target,下标 mid 即为要寻找的下标,此时不返回,收缩左侧边界接着找下一个l = mid + 1,其他情况和寻找一个数实现相同

代码实现

  1. int search(int* nums, int numsSize, int target){
  2. int l = 0;
  3. int r = numsSize - 1;
  4. while (l <= right) {
  5. int mid = l + (r - l) / 2;
  6. if (nums[mid] == target)
  7. l = mid + 1; // 找到目标值,收缩左侧边界
  8. else if (nums[mid] < target)
  9. l = mid + 1;
  10. else if (nums[mid] > target)
  11. r = mid - 1;
  12. }
  13. if (r < 0 || nums[r] != target)
  14. return -1;
  15. return r;
  16. }

题目描述

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8

输出:[3,4]

题目解析

这道题目,要找到目标值在数组中的开始位置和结束位置,就是找到 左边界和右边界,可以先找左侧边界,再以左边界为起始点,找右边界。

代码实现

  1. /**
  2. * Note: The returned array must be malloced, assume caller calls free().
  3. */
  4. int* searchRange(int* nums, int numsSize, int target, int* returnSize){
  5. int *res = (int*)malloc(sizeof(int) * 2);
  6. res[0] = -1;
  7. res[1] = -1;
  8. int left = 0;
  9. int right = numsSize - 1;
  10. *returnSize = 2;
  11. /* 找左边界 */
  12. while (left <= right) {
  13. int mid = left + (right - left) / 2;
  14. if (nums[mid] < target)
  15. left = mid + 1;
  16. else if(nums[mid] > target)
  17. right = mid - 1;
  18. else
  19. right = mid - 1;
  20. }
  21. /* 以左边界,为起始,找右边界 */
  22. if (left < numsSize && nums[left] == target) {
  23. res[0] = left;
  24. while (left < numsSize - 1) {
  25. if (nums[left + 1] == target)
  26. left++;
  27. else
  28. break;
  29. }
  30. res[1] = left;
  31. }
  32. return res;
  33. }

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号