刷题 二分查找

二分查找

二分查找的本质就是在维持区间不变量的前提下逐步缩小区间,直到找到目标元素或确认目标元素不存在
需要注意:int mid = (left + right) / 2; int mid = left + (right - left) / 2; 防止溢出
在这里插入图片描述

等于 target 的位置 | 704. 二分查找

区间不变量:

  • nums[left - 1] < target
  • nums[right + 1] > target
class Solution {
public:
    // 找等于 target 的值
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size();  
        // 循环不变量
        // nums[left - 1] < target; nums[right] > target
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            } else {
                return mid;
            }
        }
        return -1;
    }
};

插入位置 | lower_bound | 35. 搜索插入位置

class Solution {
public:
    // lower_bound 第一个大于等于 target 的位置
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            // 区间不变量:
            // + nums[left - 1] < target
            // + nums[right] >= target
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] >= target) {
                right = mid;
            }
        }
        return left;
    }
};

lower_bound | upper_bound | 34. 在排序数组中查找元素的第一个和最后一个位置

class Solution {
public:
    int lower_bound(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        // 循环不变量
        // nums[left - 1] < target; nums[right] >= target
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] >= target) {
                right = mid;
            }
        }
        // 结果是 left == right, 返回 right 和 left 都可以
        return left;
    }

    vector<int> searchRange_lower(vector<int>& nums, int target) {
        int start = lower_bound(nums, target);
        if (start == nums.size() || nums[start] != target) {
            return {-1, -1};
        }
        int end = start;
        while (end < nums.size() && nums[end] == target) {
            ++end;
        }
        return {start, end - 1};
    }

    int upper_bound(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        // 循环不变量
        // nums[left - 1] <= target; nums[right] > target
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            }
        }
        // 结果是 left == right, 返回 right 和 left 都可以
        return left;
    }

    vector<int> searchRange_upper(vector<int>& nums, int target) {
        int end = upper_bound(nums, target);
        if (end == 0 || nums[end - 1] != target) {
            return {-1, -1};
        }
        int start = end - 1;
        while (start >= 0 && nums[start] == target) {
            --start;
        }
        return {start + 1, end - 1};
    }

    vector<int> searchRange(vector<int>& nums, int target) {
        return searchRange_lower(nums, target);
        // return searchRange_upper(nums, target);
    }
};

hot 100

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

在这里插入图片描述

非常好的讲解:寻找两个正序数组的中位数

中位数的本质:将数组划分成左右相等的两个部分:

  • 总长度为偶数:中位数 = (左半部分最大值 + 右半部分最小值)/ 2
  • 总长度为奇数:中位数 = 左半部分最大值

在这里插入图片描述
在这里插入图片描述

存在一些边界情况需要考虑

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

核心要点:i + j = (m + n + 1) / 2

在这里插入图片描述

交叉判断 i 是左移还是右移

  • 什么时候 i 需要右移?nums1[i] < nums2[j-1]
  • 什么时候 i 需要左移?nums1[i-1] > nums[j]
    在这里插入图片描述

代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.size() > nums2.size()) swap(nums1, nums2);
        int m = nums1.size(), n = nums2.size();
        int left = 0, right = m;    // right = m 的原因是 i 最大可以到 m
        int half_len = (m + n + 1) / 2;
        int i = 0, j = 0;
        while (left <= right) {
            i = left + (right - left) / 2;
            j = half_len - i;
            if (i > 0 && j < n && nums1[i-1] > nums2[j]) {  // 说明 i 大了
                right = i - 1;
            } else if (j > 0 && i < m && nums1[i] < nums2[j - 1]) { // 说明 i 小了
                left = i + 1;
            } else {
                // 找到正确结果
                break;
            }
        }
        double max_left = max((i > 0) ? nums1[i-1] : INT_MIN, (j > 0) ? nums2[j - 1] : INT_MIN);
        if ((m + n) % 2 == 1) {
            return max_left;
        } else {
            double min_right = min((i < m) ? nums1[i] : INT_MAX, (j < n) ? nums2[j] : INT_MAX);
            return (min_right + max_left) / 2.0
        }
    }
};

74. 搜索二维矩阵

在这里插入图片描述

class Solution {
public:
    // 二分法的核心:缩小有效区间
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int n = matrix.size(), m = matrix[0].size();
        if (target < matrix[0][0] || target > matrix[n - 1][m - 1]) return false;

        int left = 0, right = n - 1, row = 0, col = 0;
        
        // 二分查找,先确定在哪一行
        while (left <= right) {
            // 循环不变量
            // nums[left-1][m-1] < target
            // nums[right+1][m-1] > target
            row = left + (right - left) / 2;
            if (matrix[row][m - 1] == target) {
                return true;
            } else if (matrix[row][m - 1] < target) {
                left = row + 1;
            } else {
                right = row - 1;
            }
        }
        // 循环结束
        row = left;

        // 二分查找,再确定在哪一列
        left = 0, right = m - 1;
        while (left <= right) {
            // 循环不变量
            // nums[row][left-1] < target
            // nums[row][right+1] > target
            col = left + (right - left) / 2;
            if (matrix[row][col] == target) {
                return true;
            } else if (matrix[row][col] < target) {
                left = col + 1;
            } else {
                right = col - 1;
            }
        }
        return false;
    }
};

⭐️⭐️⭐️162. 寻找峰值

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    // 首先我们可以判定,数组中必然包含至少一个峰值元素
    // 峰值元素的特征 nums[i] > nums[i-1] nums[i] > nums[i + 1]
    // 相当于找极大值点
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int left = 0, right = n - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if ((mid == 0 || nums[mid] > nums[mid - 1]) && (mid != n - 1 && nums[mid] < nums[mid + 1])) {
                left = mid + 1;
            } else if ((mid != 0 && nums[mid] < nums[mid - 1]) && (mid == n - 1 || nums[mid] > nums[mid + 1])) {
                right = mid - 1;
            } else if ((mid == 0 || nums[mid] > nums[mid - 1]) && (mid == n - 1 || nums[mid] > nums[mid + 1])) {
                return mid;
            } else {
                // 以下两种都可以
                left = mid + 1;
                // right = mid - 1;
            }
        }
        return left;
    }
};

代码优化

严格证明,简洁写法(Python/Java/C++/C/Go)
在这里插入图片描述

class Solution {
public:
    // 首先我们可以确定必然有一个峰值, 因此我们只需要进行区间缩减即可
    int findPeakElement(vector<int>& nums) {
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < nums[mid + 1]) {    // 如果 nums[mid] < nums[mid + 1],说明右侧有峰值
                left = mid + 1;
            } else {                            // nums[mid] > nums[mid + 1], 说明左侧有峰值
                right = mid;
            }
        }
        // left == right 时即为峰值位置
        return left;
    }
};

33. 搜索旋转排序数组

在这里插入图片描述

看清楚题意,是将一个升序序列旋转一下,不是两个升序序列拼在一起

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size(), left = 0, right = n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                return mid;
            } else {
                if (nums[mid] >= nums[0]) { // 在第一个上升区间 nums[mid] >= nums[0]
                    if (target > nums[mid] || target < nums[0]) left = mid + 1;
                    else right = mid - 1;
                } else { // 在第二个上升区间 nums[mid] < nums[0]
                    if (target < nums[mid] || target > nums[n - 1]) right = mid - 1;
                    else left = mid + 1;
                }
            }
        }
        return -1;
    }
};

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

在这里插入图片描述

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值