二分查找模板及题目汇总

文章目录

    • 经典二分查找模板
    • Leetcode 278
    • LeetCode374
    • LeetCode034
    • LeetCode162 寻找峰值
    • LintCode 在大数组中查找
    • LeetCode74 搜索二维矩阵

经典二分查找模板

Templete1

while(left<=right)

为什么要有等号?如果数组长度为1:[3],target=3,不加等号不能进入循环体

    public int findPosition(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int left = 0;
        int right = nums.length-1;
        int mid = 0;
        while(left<=right){
            mid = left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if (nums[mid]<target){
                left = mid+1;
            } else{
                right = mid-1;
            }
        }
        return -1;
    }

Templete2

这里right的定义为大于target的边界,所以若数组长度为1,可进入循环
right=mid 保证每次right 大于target

public int findPosition(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int left = 0;
    	//important!!!
        int right = nums.length;
        int mid = 0;
        while(left<right){
            mid = left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if (nums[mid]<target){
                left = mid+1;
            } else{
                //important!!!
                right = mid;
            }
        }
        return -1;
    }

Templete3

left+1 可能mid==target,也可能是left或者right中的一个

public int findPosition(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int left = 0;
        int right = nums.length-1;
        int mid = 0;
    	//important!!!
        while(left+1<right){
            mid = left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if (nums[mid]<target){
                left = mid+1;
            } else{
                right = mid-1;
            }
        }
    	//important!!!
        if(nums[left]==target){
            return left;
        }
        if(nums[right] == target){
            return right;
        }
        return -1;
    }

查找第一次出现target的位置

public static int findFirst(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        while (left + 1 < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        if (nums[left] == target) {
            return left;
        }
        if (nums[right] == target) {
            return right;
        }
        return -1;
    }

查找最后一次出现target的位置

 public static int findLast(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while (left + 1 < right) {
            mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        if (nums[right] == target) {
            return right;
        }
        if (nums[left] == target) {
            return left;
        }
        return -1;
    }

查找与target最接近的数的位置

public int findClosest(int[] arr, int target) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int left = 0;
        int right = arr.length - 1;
        int mid = 0;
        while (left + 1 < right) {
            mid = left + (right - left) / 2;
            //通过这个判断,如果数组中不包含target,剩余的两个值必然是离target最近的两个数
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return Math.abs(arr[left] - target) < Math.abs(arr[right] - target) ? left : right;
    }

第一个小于target的数

 public int findLargestSmallerOfTarget(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while (left + 1 < target) {
            mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        if (nums[left] == target && left != 0) {
            return left - 1;
        } else if (nums[left] == target && left == 0) {
            return -1;
        } else {
            return left;
        }
    }

第一个大于target 的数

public int findSmallesrLargetOfTargest(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;
        while (left + 1 < target) {
            mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid;
            } else {
                right = mid;
            }
        }
        if (nums[right] == target && right != nums.length - 1) {
            return right + 1;
        } else if (nums[right] == target && right == nums.length - 1) {
            return -1;
        } else {
            return right;
        }
    }

Leetcode 278

题目描述

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

代码

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right  = n;
        int mid = 0;
        //这里没有等于号:left=right时=判断终止
        while(left<right){
            mid = left+(right-left)/2;
            if(isBadVersion(mid)){
                //如果mid = true,则mid可能是最后输出值,所以必须保证mid进入到下次循环
                right = mid;
            }else{
                //mid  = false,不可能是最后结果,所以从mid+1开始下次循环
                left = mid+1;
            }
        }
        //return left 也可
        return right;
    }
}

LeetCode374

题目描述

我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-110):

-1 : 我的数字比较小
 1 : 我的数字比较大
 0 : 恭喜!你猜对了!

代码

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 1;
        int right = n;
        int mid = 0;
        //left<=right,return-1; 或left
        while(left<=right){
            mid = left+(right-left)/2;
            if(guess(mid)== 1){
                left = mid+1;
            }else if(guess(mid) == -1){
                right = mid-1;
            }else{
                return mid;
            }
        }    
        return -1;        
    }
}

LeetCode034

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

你的算法时间复杂度必须是 O(log n) 级别。

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

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums == null || nums.length==0){
            return new int[]{-1,-1};
        }
        //二分查找
        int left = 0;
        int right = nums.length-1;
        int mid = 0;
        //left+1
		//精确查找最后剩[left,mid,right]
		//范围查找最后剩[left,right]
		//如果left=target,一定是最左
		//如果right!=target ,一定没有target
        while(left+1<right){
            mid = left + (right - left)/2;
            if(nums[mid] < target){
                left = mid;
            }else{
                right = mid;
            }
        }
        int leftIndex = -1;
        if(nums[left] == target){
            leftIndex = left;
        }else if(nums[right] == target){
            leftIndex = right;
        }else{
            return new int[]{-1,-1};
        }
        int rightIndex = leftIndex;
        for(int i = leftIndex+1;i<nums.length;i++){
            if(nums[i] == target){
                rightIndex++;
            }else{
                break;
            }
        }
        
        return new int[]{leftIndex,rightIndex};
    }
}

LeetCode162 寻找峰值

峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

class Solution {
    public int findPeakElement(int[] nums) {
        int l = 0;
        int r = nums.length-1;
        int mid = 0;
        while(l<r){
            mid = l+(r-l)/2;
            if(nums[mid]>nums[mid+1]){
                r = mid;
            }else{
                l= mid+1;
            }
        }
        return l;
    }
}

LintCode 在大数组中查找

给一个按照升序排序的非负整数数组。这个数组很大以至于你只能通过固定的接口 ArrayReader.get(k) 来访问第k个数(或者C++里是ArrayReader->get(k)),并且你也没有办法得知这个数组有多大。

找到给出的整数target第一次出现的位置。你的算法需要在O(logk)的时间复杂度内完成,k为target第一次出现的位置的下标。

如果找不到target,返回-1。

public int searchBigSortedArray(ArrayReader reader, int target) {
        // write your code here
        int r = 1;
        int l = 0;
        while(reader.get(r)!=2147483647 &&  reader.get(r)<target){
            l = r;
            r = r*2;
        }
        //在l——r 上查找
        int mid = 0;
        while(l+1<r){
            mid = l + (r - l) / 2 ;
            if (reader.get(mid) == 2147483647 || reader.get(mid)>=target) {
                r = mid;
            }else {
                l = mid;
            }
        }
        if(reader.get(l) == target ){
            return l;
        }
        if(reader.get(r) == target ){
            return r;
        }
        return -1;     
    }

LeetCode74 搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。
输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true
public class Main {
    public boolean searchMatrix(int[][] matrix, int target) {
        //第一个数字小于target 直接返回false
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        //获取第一列的值
        int[] col = new int[matrix.length];
        for (int i = 0; i < matrix.length; i++) {
            col[i] = matrix[i][0];
        }
        int row = getRowNum(col, target);
        int result = getResult(matrix[row], target);
        if (result == -1) {
            return false;
        } else {
            return true;
        }
    }

    //二分查找第几行,查找小于或等于target的最大值
    public int getRowNum(int[] arr, int target) {
        int l = 0;
        int r = arr.length - 1;
        int mid = 0;
        while (l + 1 < r) {
            mid = l + (r - l) / 2;
            if (arr[mid] >= target) {
                r = mid;
            } else {
                l = mid;
            }
        }
        //反思:能不能直接返回r,好像是可以的
        if (arr[r] <= target) {
            return r;
        } else {
            return l;
        }
    }

    //二分查找第几列,没有查找到返回false
    public int getResult(int[] arr, int target) {
        int l = 0;
        int r = arr.length - 1;
        int mid = 0;
        while (l + 1 < r) {
            mid = l + (r - l) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        if (arr[l] == target) {
            return l;
        } else if (arr[r] == target) {
            return r;
        } else {
            return -1;
        }
    }
}

##旋转数组-LeetCode153 旋转排序数组最小值

mid和r比

class Solution {
    public int findMin(int[] nums) {
        //预处理
        if(nums == null || nums.length == 0){
            return Integer.MIN_VALUE;
        }
        //定义变量
        int l = 0;
        int r = nums.length-1;
        int mid;
        while(l < r ){
            mid = l + (r - l)/2;
            if(nums[mid]>nums[r]){
                 l = mid+1 ;
            }else{
                r = mid  ;
            }
        }
        return(nums[l]);
    }
}

mid和l比会出问题

class Solution {
    public int findMin(int[] nums) {
        //预处理
        if(nums == null || nums.length == 0){
            return Integer.MIN_VALUE;
        }
        //定义变量
        int l = 0;
        int r = nums.length-1;
        int mid;
        while(l< r ){
            mid = l + (r - l)/2;
            if(nums[mid]<nums[l]){
                 r = mid ;
            }else{
                //这里必须 l=mid,如果l = mid+1, [1,2] 会返回2 如果只改这里会出现死循环 
                l = mid  ;
            }
        }
        return(nums[l]);
    }  
}

##旋转数组-LeetCode 154 旋转排序数组最小值(有重复元素)

这道题是 寻找旋转排序数组中的最小值 的延伸题目。
允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

class Solution {
    public int findMin(int[] nums) {
        if(nums == null || nums.length == 0){
            return Integer.MIN_VALUE;
        }
        int left = 0;
        int right = nums.length-1;
        int mid = 0;
        while(left<right){
            mid = left + (right - left)/2;
            if(nums[mid] < nums[left]){
                right = mid;
            }else if (nums[mid] > nums[right]){
                left = mid+1;
            }else{
                //不能直接left++
                //最后如果剩下[1,2],left++,会返回2
                if(nums[left]>nums[right]){
                    left++;
                }else{
                    right--;
                }
            }
        }
        return nums[left];
        
    }
}

##旋转数组-LeetCode 33 搜索旋转排序数组(无重复元素)

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

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

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

solution

public int search(int[] nums, int target) {
        //预处理
        if(nums == null || nums.length == 0){
            return -1;
        }
        int left  = 0;
        int right = nums.length-1;
        int mid = 0;
        // <=  [1,2] 搜索2, 如果小于 搜不到2
        while(left<=right){
            mid = left + (right - left) / 2;
            if(nums[mid] == target){
                return mid;
            }
            //假设左半边有序  注意这里有等于号  
            //如果最后剩两个元素,比如【3,1】 target = 1;这里左半部分和有半部分都可看做有序
            //但是mid的值 在左半边比较才有意义 所以加了等号
            if(nums[left]<=nums[mid]){
                if(nums[left]<=target && target< nums[mid]){
                    right = mid - 1 ;
                }else{
                    left = mid + 1;
                }
            }else{
                //右半边有序
                if(nums[mid]<target && target <= nums[right]){
                    left = mid + 1;
                } else{
                    right = mid - 1;
                }
            }
        }
        return -1;
        
    }

##旋转数组-LeetCode81 搜索旋转排序数组(有重复元素)

  • 这是 上题的延伸题目,本题中的 nums 可能包含重复元素。
  • 这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?

solution

class Solution {
    public boolean search(int[] nums, int target) {
        //预处理
        if(nums == null || nums.length == 0){
            return false;
        }
        int left  = 0;
        int right = nums.length-1;
        int mid = 0;
        while(left<=right){
            mid = left + (right - left) / 2;
            if(nums[mid] == target){
                return true;
            }
            //假设左半边有序  注意这里有等于号  
            //如果最后剩两个元素,比如【3,1】 target = 1;这里左半部分和有半部分都可看做有序
            //但是mid的值 在左半边比较才有意义 所以加了等号
            if(nums[left]<nums[mid]){
                if(nums[left]<=target && target< nums[mid]){
                    right = mid - 1 ;
                }else{
                    left = mid + 1;
                }
            }else if (nums[mid] < nums[right]){
                //右半边有序
                if(nums[mid]<target && target <= nums[right]){
                    left = mid + 1;
                } else{
                    right = mid - 1;
                }
            }else{
                if(nums[left] == target){
                    return true;
                }
                left++;
            }
        }
        return false;
        
    }
}

另一个版本

class Solution {
    public boolean search(int[] nums, int target) {
        int l = 0, r = nums.length - 1;
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] == target) {
                return true;
            }
            if (nums[l] < nums[m]) {
                if (nums[l] <= target && target < nums[m]) {
                    r = m - 1;
                } else {
                    l = m + 1;
                }
            } 
            //这里是重要的不同点
            else if (nums[m] < nums[l]) {
                if (nums[m] < target && target <= nums[r]) {
                    l = m + 1;
                } else {
                    r = m - 1;
                }
            } else {
                // A[m] == A[l]
                // for example [1,3,1,1,1] target is 3
                //这里不用做判断  因为这里 num[left]==num[mid] 不可能等于target
                l++;
            }
        }
        return false;
    }
}

你可能感兴趣的:(算法,二分查找,LeetCode)