前言
本博客是对二分查找做相关练习时所做笔记,主要内容来源二分查找。
文中介绍了二分查找的三个模板以及相应题目的题解,后续会增加一些二分查找相关题目的练习。
基本介绍
- 二分查找描述了在有序集合中搜索特定值的过程。
- 使用的术语:
- 目标
target
—— 要查找的值 - 索引
Index
—— 要查找的当前位置 - 左、右指示符
left
,right
—— 我们用来维持查找空间的指标 - 中间指示符
mid
—— 用来应用条件来确定我们应该向左查找还是向右查找的索引
- 目标
- 二分查找的过程:
- 预处理 —— 如果集合未排序,则进行排序。
- 二分查找 —— 使用循环或递归在每次比较后将查找空间划分为两半。
- 后处理 —— 在剩余空间中确定可行的候选者。
二分查找的三个模板
模板一
-
代码:
int binarySearch(int[] nums, int target){ if(nums == null || nums.length == 0) return -1; int left = 0, right = nums.length - 1; while(left <= right){ // 此形式可防止 (left + right) 溢出 int mid = left + (right - left) / 2; if(nums[mid] == target){ return mid; } else if(nums[mid] < target) { left = mid + 1; }else { right = mid - 1; } } // End Condition: left > right return -1; }
-
说明:
这是最基本的二分查找形式。
- 初始条件:
left = 0, right = length-1
- 终止:
left > right
- 向左查找:
right = mid-1
- 向右查找:
left = mid+1
- 初始条件:
33.搜索旋转排序数组
-
题目描述:给你一个整数数组 nums ,和一个整数 target 。
该整数数组原本是按升序排列,但输入时在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
-
示例
输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4
-
分析:
直接搜索过于简单,主要是二分搜索。
二分搜索的关键在于确定要搜索的元素在左半部还是右半部。
由于旋转后
nums[0]>nums[nums.length-1]
,故可利用这一点首先确定nums[mid]在旋转后的左半段还是在右半段。如果在左半段,nums[mid]左边的部分肯定是有序的,若
nums[left]<=target<nums[mid]
,则在[left,mid-1]中搜索,否则在[mid+1,right]中搜索;如果在右半段,nums[mid]右边的部分肯定是有序的,若
nums[mid]<target<=nums[right]
,则在[mid+1,right]中搜索,否则在[left,mid-1]中搜索。这样,即完成了二分。
-
代码:
class Solution { public int search(int[] nums, int target) { int n = nums.length; int left = 0, right = n-1; while(left <= right) { int mid = left + (right-left)/2; if(nums[mid] == target) { return mid; } //判断target在左边还是右边 //首先判断nums[mid]在旋转的左半段还是右半段 if(nums[mid] >= nums[0]) { //nums[mid]在左半段 if(target>=nums[left] && target<nums[mid]) { //target在左边 right = mid - 1; }else { //target在右边 left = mid + 1; } }else { //nums[mid]在右半段 if(target<=nums[right] && target>nums[mid]) { left = mid + 1; }else { right = mid - 1; } } } return -1; } }
模板二
-
代码:
int binarySearch(int[] nums, int target){ if(nums == null || nums.length == 0) return -1; int left = 0, right = nums.length; while(left < right){ // Prevent (left + right) overflow int mid = left + (right - left) / 2; if(nums[mid] == target){ return mid; } else if(nums[mid] < target) { left = mid + 1; } else { right = mid; } } return -1; }
-
说明:适用于查找第一个满足条件的元素的情况,或者要查找的元素一定在数组中,通过不断缩小范围定位到那个元素
- 初始条件:left = 0, right = length
- 终止:left == right
- 向左查找:right = mid
- 向右查找:left = mid+1
比如用于查找第一个大于等于target的数。
int left = 0, right = nums.length; while(left < right){ // 防止 (left + right) 溢出 int mid = left + (right - left) / 2; if(nums[mid] < target) { left = mid + 1; } else { right = mid; } } return left;
162.寻找峰值
-
题目描述:峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设
nums[-1] = nums[n] = -∞
。 -
示例:
输入: nums = [1,2,3,1] 输出: 2 解释: 3 是峰值元素,你的函数应该返回其索引 2。
-
分析:
判断一个数是否为峰值,由于
nums[-1] = nums[n] = -∞
,若从头开始遍历,只需要检查它是否大于下一个元素 nums[i+1] 即可判断 nums[i]是否是峰值,也就是说第一个满足nums[i]>nums[i+1]
的数nums[i]必为峰值。利用此特性,可以进行二分查找。
若nums[mid]>nums[mid+1],则左半部(left,mid)必有峰值;
若nums[mid]<nums[mid+1],则右半部(mid+1,right)必有峰值;
-
代码:
class Solution { public int findPeakElement(int[] nums) { int left = 0, right = nums.length-1; while(left < right) { int mid = left + (right-left)/2; if(nums[mid] < nums[mid+1]) left = mid + 1; if(nums[mid] > nums[mid+1]) right = mid; } return left; } }
模板三
-
代码:
int binarySearch(int[] nums, int target) { if (nums == null || nums.length == 0) return -1; int left = 0, right = nums.length - 1; while (left + 1 < right){ // Prevent (left + right) overflow int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid; } else { right = mid; } } // Post-processing: // End Condition: left + 1 == right if(nums[left] == target) return left; if(nums[right] == target) return right; return -1; }
-
说明:多用于要查找的值需要比较左右的场景,比如从有序数组中找到距离x最近的值
可用于搜索需要访问当前索引及其直接左右邻居索引的时候。
- 初始条件:left = 0, right = length-1
- 终止:left + 1 == right
- 向左查找:right = mid
- 向右查找:left = mid
658.找到K个最接近的元素
-
题目描述:给定一个排序好的数组
arr
,两个整数k
和x
,从数组中找到最靠近x
(两数之差最小)的k
个数。返回的结果必须要是按升序排好的。整数
a
比整数b
更接近x
需要满足:|a - x| < |b - x|
或者|a - x| == |b - x|
且a < b
-
示例:
输入:arr = [1,2,3,4,5], k = 4, x = 3 输出:[1,2,3,4]
-
分析:
可先用二分查找找到最接近
x
的位置key
,然后以key
为中心,找到最靠近x
的k
个数。二分查找可利用模板三,最终将位置锁定在left和right之间,再判断二者谁最接近
x
。 -
代码:
class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { List<Integer> ans = new ArrayList<>(); //以距离x最近的值为起点,向两端寻找距离x最近的k个值 int left = binarySearch(arr,x); int right = left; for(int i=1; i<k; i++) { //左边已到达端点 if(left == 0) right++; //右边已到达端点 else if(right == arr.length-1) left--; else if(x-arr[left-1] <= arr[right+1]-x){ //左边离的较近 left--; } else { //右边离的较近 right++; } } for(int i=left; i<=right; i++) { ans.add(arr[i]); } return ans; } //二分查找找到距离x最近的值 public int binarySearch(int[] arr, int x) { int left = 0; int right = arr.length - 1; //循环终止时距离x最近的值在left与right之间 while(left < right-1) { int mid = left + (right - left) / 2; if(arr[mid] == x) return mid; else if(arr[mid] < x) { //距离x最近的值在区间[mid,right]中 left = mid; } else { //距离x最近的值在区间[left,mid]中 right = mid; } } if(x-arr[left] <= arr[right]-x) return left; else return right; } }