定义
二分查找也称折半搜索,是一种在有序数组中查找某一特定的元素的搜索算法。
二分查找最基本的代码
class Solution {
public int search(int[] nums, int target) {
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]==target){
return mid;
}else if(nums[mid]<target){
left=mid+1;
}else{
right=mid-1;
}
}
return -1;
}}
第一个该注意的问题
mid=(left+right)/2;
代码在left和right都很大的时候会出现溢出的情况,从而导致数组访问溢出。
改进方法将加法变为减法:mid=left+(right-left)/2;
或者是使用神奇的位运算: mid=left+((right-left)>>>1);
了解了二分查找的基本知识后通过题目来加深认识
题目一:leetcode 704.二分查找
看题目的题目的意思就是最基本的二分查找直接撸代码
class Solution {
public int search(int[] nums, int target) {
int i=0;int j=nums.length-1;
while(i<=j){
int mid=i+(j-i)/2;
if(nums[mid]==target){
return mid;
}else if(nums[mid]>target){
j=mid-1;
}else{
i=mid+1;
}
}
return -1;
}
}
通过了确实是通过了 然后我跑去瞄了一下最快的代码发现他和我的区别就是计算mid的时候没有使用防止溢出的方法。
题目二:leetcode 34
题目描述:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
这个题目的基本思路:
第一步用二分查找找出到一个与target数值相同的元素并记录下标,
然后查找它的左边和右边,直到碰到与target不同的值记录下标。
基本思路有了 好了直接上代码!
class Solution {
public int[] searchRange(int[] nums, int target) {
if(nums==null||nums.length==0){//如果数组是空数组,直接返回-1
return new int []{-1,-1};
}
int start=0;
int end=nums.length-1;
int mid=0;
int index=-1;
while(start<=end){//开始二分查找,当找到的时候循环结束
mid=start+(end-start)/2;
// mid = start + ((end-start)>>1);
if(nums[mid]==target){
index=mid;
break;
}else if(nums[mid]>target){
end=mid-1;
}else{
start=mid+1;
}
}
start=index;
end=index;
if(index!=-1){
while(start>0&&nums[start-1]==target){//像左边扫描
start--;
}
while(end<nums.length-1&&nums[end+1]==target){//向右边扫描
end++;
}
}
return new int []{start,end};
}
}
过了之后还是去瞟了一下速度最快的代码发现思路都差不多
题目三:leetcode 153
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
思路:看到这个题第一步觉得自己可以抖个机灵直接排序一下输出最小的
class Solution {
public int findMin(int[] nums) {
Arrays.sort(nums);
return nums[0];
}
}
还过了。。。。但是要3ms好慢的感觉,老老实实的用二分
官方二分解法
class Solution {
public int findMin(int[] nums) {
if(nums.length==1){//如果数组的长度等于1,直接返回第一位。
return nums[0];
}
int i=0;
int j=nums.length-1;
if(nums[j]>nums[0]){//如果数组最后一位大于第一位数组未旋转直接返回第一位。
return nums[0];
}
while(i<=j){
int mid=i+(j-i)/2;
if(nums[mid]>nums[mid+1]){//如果nums[mid]比它右边的一位大 那么它右边的一位就是最小的
return nums[mid+1];
}
if(nums[mid-1]>nums[mid]){//如果nums[mid]比它左边的一位小 那么它就是最小的
return nums[mid];
}
if(nums[mid]>nums[0]){
i=mid+1;
}else{
j=mid-1;
}
}
return -1;
}
}
官方的解法思路清晰一看就懂,但是码量太多了
这里有一种简单不容易懂的解法
class Solution {
public int findMin(int[] nums) {
int i=0;
int j=nums.length-1;
while(i<j){
int mid=i+(j-i)/2;
if(nums[mid]<nums[j]){
j=mid;
}else{
i=mid+1;
}
}
return nums[i];
}
}
题目四:leetcode 162
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
class Solution {
public int findPeakElement(int[] nums) {
int i=0;
int j=nums.length-1;
while(i<j){
int mid=i+(j-i)/2;
if(nums[mid]>nums[mid+1]){
j=mid;
}else {
i=mid+1;
}
}
return i;
}
}
思路:每次计算mid,如果mid是递增的,则结果在mid右边;如果mid是递减的,则结果在mid左边 。
因为答案是返回任意峰值,所以代码比较少。
题目五:leetcode 74
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix==null||matrix.length==0){
return false;
}
int row=0;
int col=matrix[0].length-1;
while(row<matrix.length&&col>=0){
if(matrix[row][col]==target){
return true;
}else if(matrix[row][col]<target){
row++;
}else{
col--;
}
}
return false;
}
}
由于之前未接触二维数组中使用二分查找所以这个题疯狂的找题解和找评论区,
简单点来将就是利用矩阵升序的特点,因为从左到右升序, 从上到下升序。所以每次可以去掉一行 / 一列。
另一种解法就是通过将二维数组直接将为一维数组,然后进行二分查找
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
if (m == 0) return false;
int n = matrix[0].length;
// 二分查找
int left = 0, right = m * n - 1;
int pivotIdx, pivotElement;
while (left <= right) {
pivotIdx = (left + right) / 2;
pivotElement = matrix[pivotIdx / n][pivotIdx % n];
if (target == pivotElement) return true;
else {
if (target < pivotElement) right = pivotIdx - 1;
else left = pivotIdx + 1;
}
}
return false;
}
}
总结:二分查找要注意的地方就是mid的定义和while()循环中的判断条件。