hot100二分查找专题

代码框架

核心的是寻找问题的"两段性",以及精准把控让无数人头疼的边界条件(while 里面到底带不带等号,right 到底等不等于 mid)。

cpp 复制代码
// 基础二分查找框架 (左右闭区间 [left, right])
int binarySearch(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1; 
    
    // 因为是闭区间,当 left == right 时区间依然有效,所以用 <=
    while (left <= right) { 
        int mid = left + (right - left) / 2; // 防溢出写法
        
        if (nums[mid] == target) {
            return mid; // 找到目标(或者在这里根据题意继续收缩左右边界)
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右侧,更新左边界
        } else if (nums[mid] > target) {
            right = mid - 1; // 目标在左侧,更新右边界
        }
    }
    return -1; // 没找到或者返回最终所需的边界
}

搜索插入位置

循环结束时,一定是 left = right + 1

此时的 left 正好指向第一个大于 target 的位置,即最终的插入位置

哪怕 target 比所有元素都大,left 最后也会越界停在 nums.size() 的位置
核心都是理解 while 循环结束后 left 和 right 指针到底停在了什么状态。

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left  = 0;
        int right = nums.size()-1;

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

搜索二维矩阵

由于本题的数字大小特殊性

每行中的整数从左到右按非严格递增顺序排列。

每行的第一个整数大于前一行的最后一个整数。

本质上就是一个m*n的一维数组

难点就是找到重点了应该如何去变换成二维数组的坐标

行号:mid / n (看它前面完整排满了多少行)

列号:mid % n (看它在当前行排在第几个)

重点就是下面这一句

cpp 复制代码
int midValue = matrix[mid / n][mid % n];
cpp 复制代码
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (matrix.empty() || matrix[0].empty()) return false;
        int left = 0;
        int m = matrix.size();
        int n = matrix[0].size();
        int right = m*n-1;

        while(left<=right){
            int mid = left +(right-left)/2;
            int midValue = matrix[mid / n][mid % n];
            if (midValue == target){
                return true;
            }else if(midValue<target){
                left = mid+1;
            }else if(midValue >target){
                right = mid-1;
            }
        }
        return false;
    }
};

题外话判断空条件

警报一:想要访问具体索引时(如 nums[0]、matrix[0][0])

如果给你的是个空数组 [],你强行去拿 nums[0],直接越界。
警报二:想要获取嵌套/内部结构的属性时(如二维数组的列数)

就像上一题里的 matrix[0].size()。求矩阵的列数,前提是你得有"第一行"。如果 matrix 本身是空的 [],你试图去调取不存在的 matrix[0] 的大小,程序立刻就炸了。但如果你只是求最外层的 matrix.size(),通常是安全的(空数组会乖乖返回 0)。
警报三:想要解引用指针时(如树和链表问题中的 node->val)

只要你想用箭头 -> 或者点 . 去读取属性,左边的对象绝对不能是 null 或空指针。

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

当我们用基础框架找到 nums[mid] == target 时,我们不能再像以前那样直接 return mid 了,因为这个 mid 可能只是连续目标值中间的某一个,并不是我们要找的"开始位置"或"结束位置"。

寻找开始位置(左边界): 当我们发现 nums[mid] == target 时,说明当前找到了目标,但它左边可能还有目标。 先用一个变量把当前这个 mid 记下来,然后绝不手软地砍掉右半边(让 right = mid - 1),继续在左半区间寻找有没有更早出现的目标值。 直到循环结束,我们记录下来的就是最左边的位置。

寻找结束位置(右边界):同理,当我们发现 nums[mid] == target 时,说明当前找到了目标,但它右边可能还有。我们同样先记下当前的 mid,然后砍掉左半边(让 left = mid + 1),继续去右半区间探索有没有更晚出现的目标值。

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.empty()){
            return {-1,-1};
        }
        int first = firstlocal(nums,target);
        int last = lastlocal(nums,target);
        return {first,last};
    }

    int firstlocal(vector<int>& nums,int target){
        int left = 0;
        int right = nums.size()-1;

        int firstpos = -1;

        while(left<=right){
            int mid = left+(right-left)/2;
            if(nums[mid] == target){
                firstpos = mid;
                right = mid-1;
            }else if(nums[mid]>target){
                right = mid-1;
            }else if(nums[mid]<target){
                left = mid+1;
            }
        }
        return firstpos;
    }
    int lastlocal(vector<int>& nums,int target){
        int left = 0;
        int right = nums.size()-1;

        int lastpos = -1;

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

搜索旋转排序数组

如果要去找到旋转点的话,时间复杂度过高,题目要求logn的时间复杂度

二分查找的高阶思维------不一定要全局有序,只要具备"局部有序性",照样能用二分!

核心思想是在修改基础框架的中的if判断操作这一部分

首先切割层两份,一份必定有序,一份可能有序

如果目标出现在有序部分中,则直接使用二分

如果目标出现无序部分中,则继续切割,继续查看目标是否在有序部分中

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;

        while(left<= right){
            int mid = left +(right-left)/2;
            if(nums[mid] == target) return 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;

    }
};

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

结合了旋转和找起始位置(记录并收缩)的结合

优化条件,要是整体有序,则直接返回left即可

修改框架中的if判断的条件,现在是判断有序无序

如果左边部分有序,则最小值(断崖)在右半部分,继续判断

如右半部分有序,则最小值,在左半部分。

继续这样分为有序和无序,然后将nums[mid]和minval判断最终得到最小值

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size()-1;
        int  minval = INT_MAX;

        while(left<=right){
            if(nums[left] <= nums[right]){
                minval = min(minval,nums[left]);
                break;
            }

            int mid = left+(right-left)/2;

            minval = min(minval,nums[mid]);

            if(nums[left]<=nums[mid]){
                left = mid+1;
            }else{
                right = mid-1;
            }
        }
        return minval;
    }
};

寻找两个正序数组的中位数

  • 什么是中位数?中位数的本质是把一个集合划分为长度相等的两个部分,并且左半部分的最大值 ≤\le≤ 右半部分的最小值。
  • 既然有两个数组 nums1 和 nums2,我们可以想象在这两个数组中各切一刀,把它们都分成左右两半。
  • 把 nums1 的左半边和 nums2 的左半边拼起来,作为总体的"左半部分";把右半边拼起来,作为总体的"右半部分"。
  • 核心规律(两段性):假设我们在较短的数组 nums1 中,切在第 iii 个位置(即 nums1 贡献了 iii 个元素给左半部分)。为了保证总体左右两部分长度相等(或者左边多一个),nums2 必须贡献的元素个数 jjj 也就随之确定了:j=m+n+12−ij = \frac{m + n + 1}{2} - ij=2m+n+1−i。
  • 二分游走判断:我们拿基础框架去二分寻找这个正确的切口 iii:
  • 切口周围有四个关键元素:nums1 切口左边的最大值 L1L_1L1,右边的最小值 R1R_1R1;nums2 切口左边的最大值 L2L_2L2,右边的最小值 R2R_2R2。
  • 如果 L1>R2L_1 > R_2L1>R2: 说明我们在 nums1 里的切口太靠右了,给左半部分贡献了太多大元素,得向左边收缩:right = i - 1。
  • 如果 L2>R1L_2 > R_1L2>R1: 说明我们在 nums1 里的切口太靠左了,得向右边收缩:left = i + 1。
  • 如果交叉比较都满足(L1≤R2L_1 \le R_2L1≤R2 且 L2≤R1L_2 \le R_1L2≤R1): 恭喜你,找到了完美的黄金分割线!直接计算中位数即可。
cpp 复制代码
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size() > nums2.size()){
            return findMedianSortedArrays(nums2,nums1);
        }

        int m = nums1.size();
        int n = nums2.size();

        int left = 0;
        int right = m;

        while(left<=right){
            int i = left + (right-left)/2;
            int j = (m+n+1)/2 -i;
            int nums1Lmax = (i == 0)? INT_MIN : nums1[i-1];
            int nums1Rmin = (i == m) ? INT_MAX : nums1[i];
            int nums2Lmax = (j == 0) ? INT_MIN :nums2[j-1];
            int nums2Rmin = (j==n)? INT_MAX : nums2[j];

            if(nums1Lmax <= nums2Rmin && nums2Lmax <= nums1Rmin){
                if((m+n)%2 == 0){
                    return (max(nums1Lmax,nums2Lmax)+min(nums2Rmin,nums1Rmin)) / 2.0;
                }else{
                    return max(nums1Lmax,nums2Lmax);
                }
            }else if(nums1Lmax>nums2Rmin){
                right = i-1;
            }else if(nums2Lmax > nums1Rmin){
                left = i+1;
            }
        }
        return 0.0;
    }
};
相关推荐
YGGP1 小时前
【Golang】LeetCode 54. 螺旋矩阵
算法·leetcode·矩阵
代码改善世界2 小时前
【数据结构与算法】顺序表和链表题解
数据结构·链表
十八岁讨厌编程2 小时前
【算法训练营 · 二刷总结篇】贪心算法、图论部分
算法·贪心算法·图论
没有医保李先生2 小时前
嵌入式面试八股文整理(持续更新)
算法
mit6.8242 小时前
ai五层结构
算法
F_D_Z2 小时前
最长连续序列的长度LongestConsecutive
算法·哈希表·最长连续序列
DeepModel2 小时前
【回归算法】广义线性模型(GLM)详解
人工智能·算法·回归
沪漂阿龙2 小时前
大模型采样策略终极指南:Top-k、Top-p与结构化输出最佳实践
人工智能·算法·机器学习
DeepModel2 小时前
【回归算法】局部加权回归(LWR)详解
人工智能·算法·回归