LeetCode Hot100(一)二分查找

一、二分查找的五个模版

1.模版一:基础查找:目标是否存在

java 复制代码
/* 模板一:基础查找,目标是否存在 */

class Solution {
    public int binarySearch(int[] nums, int target) {
        int n = nums.length;
        if (n == 0)
            return -1;
        if (target < nums[0] || target > nums[n - 1])
            return -1;

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target)
                left = mid + 1;
            else if (nums[mid] > target)
                right = mid - 1;
            else
                return mid;
        }
        return -1;
    }
}

2.模版二:查找左边界(第一个>=target的元素位置)

解法一,边界的更新逻辑:

  • 当 nums[mid] < target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的左侧。因此,更新 left = mid + 1。
  • 当 nums[mid] >= target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠左的答案,因此,更新 right= mid - 1。

解法一,返回边界 left:

  • 由于 mid 满足条件时,总是更新 right,试图寻找更靠左的满足条件的答案,因此,当循环终止时,right 位置一定不满足条件。
  • left 要么不发生移动,要么是向右、向待查找区间内移动,即满足条件的区间。
  • 因此,当循环终止时,left 停在最靠前的满足条件(>= target)的位置,并且,left 左侧的元素均不满足(均 < target)。
java 复制代码
/* 模板二:查找左边界(第一个 >= target 的元素位置) */

class Solution {
    public int lowerBound(int[] nums, int target) {
        int n = nums.length;
        if (n == 0)
            return 0;
        if (target <= nums[0])
            return 0;
        if (target > nums[n - 1])
            return n;

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target)
                left = mid + 1;
            else  
                right = mid - 1;
        }
        return left;
    }
}

3.模版三:查找大于的左边界(第一个>target的元素位置)

解法一,边界的更新逻辑:

  • 当 nums[mid] <= target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的左侧。因此,更新 left = mid + 1。
  • 当 nums[mid] > target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠左的答案,因此,更新 right= mid - 1。

解法一,返回边界 left:

  • 由于 mid 满足条件时,总是更新 right,试图寻找更靠左的满足条件的答案,因此,当循环终止时,right 位置一定不满足条件。
  • left 要么不发生移动,要么是向右、向待查找区间内移动,即满足条件的区间。
  • 因此,当循环终止时,left 停在最靠前的满足条件(> target)的位置,并且,left 左侧的元素均不满足(均 <= target)。
java 复制代码
class Solution {
    public int upperBound(int[] nums, int target) {
        int n = nums.length;
        if (n == 0)
            return 0;
        if (target < nums[0])
            return 0;
        if (target >= nums[n - 1])
            return n;

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target)
                left = mid + 1;
            else 
                right = mid - 1;
        }
        return left;
    }
}

4.模版四:查找右边界(最后一个<=target的元素)

解法一,边界的更新逻辑:

  • 当 nums [mid] <= target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠右的答案,因此,更新 left = mid + 1。
  • 当 nums [mid] > target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的右侧。因此,更新 right。

解法一,返回边界 right

  • 由于 mid 满足条件时,总是更新 left,试图寻找更靠右的满足条件的答案,因此,当循环终止时,left 位置一定不满足条件。
  • right 要么不发生移动,要么是向左、向待查找区间内移动,即满足条件的区间。
  • 因此,当循环终止时,(写法一中的)right 停在最靠后的满足条件(<= target)的位置,并且,right 右侧的元素均不满足(均 > target)。
java 复制代码
class Solution {
    public int biSeLessEqualLast(int[] nums, int target) {
        int n = nums.length;
        if (n == 0)
            return -1;
        if (target < nums[0])
            return -1;
        if (target >= nums[n - 1])
            return n - 1;

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target)
                left = mid + 1;
            else  
                right = mid - 1;
        }
        return right;  
    }
}

5.模版五:查找小于的右边界(最后一个 < target 的元素位置)

解法一,边界的更新逻辑:

  • 当 nums [mid] < target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠右的答案,因此,更新 left = mid + 1。
  • 当 nums [mid] >= target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的右侧。因此,更新 right。

解法一,返回边界 right

  • 由于 mid 满足条件时,总是更新 left,试图寻找更靠右的满足条件的答案,因此,当循环终止时,left 位置一定不满足条件。
  • right 要么不发生移动,要么是向左、向待查找区间内移动,即满足条件的区间。
  • 因此,当循环终止时,right 停在最靠后的满足条件(< target)的位置,并且,right 右侧的元素均不满足(均 >= target)。
java 复制代码
class Solution {
    public int biSeLessLast(int[] nums, int target) {
        int n = nums.length;
        if (n == 0)
            return -1;
        if (target <= nums[0])
            return -1;
        if (target > nums[n - 1])
            return n - 1;

        int left = 0;
        int right = n - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target)
                left = mid + 1;
            else 
                right = mid - 1;
        }
        return right; 
    }
}

二、二分查找例题

1.搜索旋转排序数组(33题)

题目要求 O(logN) 的时间复杂度,基本可以断定本题是需要使用二分查找 ,怎么分是关键。

由于题目说数字了无重复,举个例子:

1 2 3 4 5 6 7 可以大致分为两类,

第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。

这种情况下,前半部分有序 。因此如果 nums[start] <=target<nums[mid],则在前半部分找 ,否则去后半部分找。

第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。

这种情况下,后半部分有序 。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。

java 复制代码
class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        int left=0;
        int right=nums.length-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target) return mid;
            if(nums[mid]>=nums[left]){
                if(target>=nums[left]&&target<nums[mid]){
                    right=mid-1;
                }
                else{
                    left=mid+1;
                }
            }else{
                 if (target <= nums[right] && target > nums[mid]) {
                    left = mid + 1;
                } else {
                    right= mid - 1;
                }

            }
        }
        return -1;
    }
    
}

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

使用模版二找到第一个位置,使用模版三找到最后一个位置

java 复制代码
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int m=-1;
        int n=-1;
        int start=0,end=nums.length-1;
        while(start<=end){
            int mid=start+(end-start)/2;
            if(nums[mid]>=target){
                end=mid-1;
            }
            else start=mid+1;
        }
        // 关键校验:如果所有的元素都比target大,start会等于nums.lenghth,
//所以需要校验start是否越界?nums[start]是否等于target?
        if (start < nums.length && nums[start] == target) {
            m = start; // 只有存在target时,才赋值左边界
        } else {
            // 左边界都不存在,直接返回[-1,-1],不用找右边界了
            return new int[]{-1, -1};
        }
        start=0;
        end=nums.length-1;
        while(start<=end){
            int mid=start+(end-start)/2;
            if(nums[mid]<=target){
                start=mid+1;
            }
            else end=mid-1;
        }
        n=end;
        return new int[]{m,n};

    }
}

3.寻找重复数(287题)

我们先累计数值在 [1,⌊n/2​⌋]的数字,如果数值在 [1,⌊n/2​⌋] 之间的数字个数超过了n/2,就证明重复的值在 [1,⌊n/2​⌋]区间,就让end=mid-1,否则就是重复数字的值在后半部分区间,让start=mid+1。

java 复制代码
class Solution {
    public int findDuplicate(int[] nums) {
        // 数值范围是1~n(数组长度为n+1),所以start从1开始,而非0
        int start = 1;
        int end = nums.length - 1;
        
        while (start <= end) {
            // 取数值范围的中间值(不是数组索引的中间值)
            int mid = start + (end - start) / 2;
            // 统计数组中 <= mid 的元素个数
            int count = 0;
            for (int v : nums) {
                if (v <= mid) {
                    count++;
                }
            }
            
            // 核心逻辑:如果<=mid的数超过mid个,说明重复值在[start, mid]区间
            if (count > mid) {
                end = mid - 1; // 收缩右边界,找左半区
            } else {
                start = mid + 1; // 重复值在[mid+1, end]区间
            }
        }
        
        // 循环结束时,start == end+1,start就是重复的数字
        return start;
    }
}

4.搜索二维矩阵(240题)

这就是把二分查找变成二维的,基本的思想没有变。

java 复制代码
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
        return searchMatrixs(matrix,0,0,matrix.length-1,matrix[0].length-1,target);
    }
    public boolean searchMatrixs(int[][] matrix,int x1,int y1,int x2,int y2,int target){
        if(x1>x2 || y1>y2) return false;
        int x=x1+(x2-x1)/2;
        int y=y1+(y2-y1)/2;
        if(matrix[x][y]==target) return true;
        else if(matrix[x][y]>target) return searchMatrixs(matrix,x1,y1,x-1,y2,target) || searchMatrixs(matrix,x1,y1,x2,y-1,target);
        else return searchMatrixs(matrix,x+1,y1,x2,y2,target) || searchMatrixs(matrix,x1,y+1,x2,y2,target);
    }
}
相关推荐
六义义2 小时前
java基础十二
java·数据结构·算法
四维碎片2 小时前
QSettings + INI 笔记
笔记·qt·算法
Tansmjs2 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
独自破碎E3 小时前
【优先级队列】主持人调度(二)
算法
weixin_445476683 小时前
leetCode每日一题——边反转的最小成本
算法·leetcode·职场和发展
Swift社区4 小时前
LeetCode 385 迷你语法分析器
算法·leetcode·职场和发展
sonadorje4 小时前
svd在图像处理中的应用
算法
挖矿大亨4 小时前
c++中的函数模版
java·c++·算法
测试老哥4 小时前
软件测试之功能测试详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例