Loading

04 二分查找

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

image
image

1.1 解题思路

  • 暴力做法:遍历数组,时间复杂度 \(O(n)\)
  • 利用数组有序的性质二分查找:

1.2 代码实现

点击查看代码
class Solution {
public:
    // 左闭右闭写法
    int lower_bound(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] < target) {
                l = m + 1; // l 指向的元素一定 >= target
            } else {
                r = m - 1; // r 指向的元素一定 < target
            }
        }
        return l; // r + 1 = l
    }
    int upper_bound(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] < target + 1) {
                l = m + 1; // l 指向的元素一定 >= target + 1
            } else {
                r = m - 1; // r 指向的元素一定 < target + 1
            }
        }
        return r; // r + 1 = l
    }
    vector<int> searchRange(vector<int>& nums, int target) {
        int l = lower_bound(nums, target);
        if (l == nums.size() || nums[l] != target) {
            return {-1, -1};
        } 
        int r = upper_bound(nums, target);
        return {l, r};  
    }
};
  • 时间复杂度:$$O(logn)$$
  • 空间复杂度:$$O(1)$$

2. 寻找峰值

image
image

2.2 解题思路

  1. 相邻元素必不相等。
  2. 数组中一定存在峰值
  3. 由于\(nums[n] = -\infty\),所以nume[n - 1]有两种情况:
    • nums[n - 1] > nums[n - 2],也即nums[n - 1]为峰顶
    • nums[n - 1] < nums[n - 2]nume[n - 1]不是峰顶

结合条件2可知,如果我们在[0, n - 2]中没有找到峰顶元素,那么nums[n - 1]一定是峰顶。所以我们先假设nums[n - 2]不是峰顶,或者说,nums[n - 2]位于峰顶右侧。
问题:查找【目标峰顶】位置

2.3 代码实现

点击查看代码
class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        // 红蓝染色法
        int l = 0, r = n - 2; // 红色表示,目标峰值左侧
        // 蓝色表示, 目标峰值及右侧
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] < nums[m + 1]) {
                l = m + 1;
            } else {
                r = m - 1;
            }
        }
        return l;
    }
};
- 时间复杂度:$$O(logn)$$ - 空间复杂度:$$O(1)$$

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

image
image
image

3.1 解题思路

相当经典的一道题,我还记得一个典型的例子
[1 1 0 1][1 0 1 1]
不过这道题说了,数组中的元素互不相同。

我们需要一个判定方式来判断nums[mid]是在最小值的左侧还是右侧。
红蓝染色法:红色表示最小值左侧元素,蓝色表示最小值及其右侧元素
由于最小值一定位于数组中,那么和上一道题一样,最后1个元素有两种可能:

  • 是最小值
  • 位于最小值右侧

无论是哪种情况,都被染成蓝色

所以我们在0~n - 2中二分,如果nums[mid] < 最后1个数,那么

  • 它可能位于一段递增的数组中
  • 或者在两段递增数组中的第二段

无论是上述哪种情况,该元素都

  • 要么是最小值
  • 要么位于最小值右侧

无论如何,都应该被染成蓝色

由于数组中的元素互不相同,所以如果nums[mid] > 最后1个数,那么

  • 不存在只有一段递增数组的情况
  • 只能在两段递增数组中的第一段

因此nums[mid]一定在最小值的左侧,应该被染成红色

3.2 代码实现

点击查看代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        int l = 0, r = n - 2;
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] < nums[n - 1]) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }
        return nums[l]; 
    }
};
- 时间复杂度:$$O(logn)$$ - 空间复杂度:$$O(1)$$

4. 搜索旋转排序数组

image
image
image

4.1 解题思路

  1. 两次二分
    受到上一道题的启发,我们可以先在这个旋转过的数组中找到数组中的最小值,记作i。然后根据targetnums[i]的大小,进而判断target可能位于两段区间中的哪一段,再在这一段区间中二分查找(当然,也可能只有一段,这种情况更简单,直接二分查找)
  2. 一次二分
    需要分类讨论。
    考虑红蓝染色法,红色表示位于target左侧,蓝色表示target及target右侧
    最后1个数一定被染成蓝色。
    我们思考一下什么时候nums[mid]和它的右侧被染成蓝色
    第1种情况,如果nums[mid] > nums[n - 1],那么nums[mid]只能位于前半个递增段。
    此时如果target > nums[n - 1],那么就位于同一段。
  • nums[mid] >= targetmid和mid右边就被染成蓝色
    第2种情况,如果nums[mid] <= nums[n - 1],那么nums[mid]要么处于后半个递增段,要么整个数组都是递增的。
  • 此时如果target > nums[n - 1],那么target只能位于前半个递增段(有两段),此时mid及其右边就被染成蓝色
  • 如果nums[mid] >= target,也被染成蓝色

4.2 代码实现

  1. 两次二分
点击查看代码
class Solution {
public:
    int search_min(vector<int>& nums) {
        int n = nums.size();
        int l = 0, r = n - 2;
        // 红色表示最小值左侧,蓝色表示最小值及其右侧
        // 最后1个数要么就是最小值,要么是最小值右侧,因此一定被涂成蓝色
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] < nums[n - 1]) {
                r = m - 1; // nums[m]可能是最小值,也可能位于最小值右侧
            } else {
                l = m + 1;
            }
        }
        return l; // r + 1
    }
    int search(vector<int>& nums, int target) {
        // 两次二分
        int idx = search_min(nums);
        int n = nums.size();
        auto binary_search = [&](int left, int right) -> int {
            // 红蓝染色法:位于target左侧被染成红色,target及target右侧被染成蓝色
            int right_bound = right + 1;
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (nums[mid] < target) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            if (left == right_bound) {
                return -1;
            }
            if (nums[left] != target) {
                return -1;
            }
            return left;
        };
        if (nums[idx] == target) {
            return idx;
        } else if (nums[idx] > target) {
            return -1;
        }
        int ans = binary_search(0, idx - 1);
        if (ans != -1) {
            return ans;
        }
        return binary_search(idx + 1, n - 1);
    }
};
- 时间复杂度:$$O(logn)$$ - 空间复杂度:$$O(1)$$ 2. 一次二分
点击查看代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 2;
        while (l <= r) {
            int m = l + (r - l) / 2;
            auto check = [&](int m) -> bool {
                int x = nums[m];
                if (x >= target && target > nums[n - 1]) {
                    return true;
                }
                return x <= nums[n - 1] && (target > nums[n - 1] || x >= target);
            };
            if (check(m)) {
                r = m - 1; // m和m右边被染成蓝色
            } else {
                l = m + 1;;
            }
        }
        if (l == n || nums[l] != target) {
            return -1;
        }
        return l;
    }
};
  • 时间复杂度:$$O(logn)$$
  • 空间复杂度:$$O(1)$$
posted @ 2025-12-24 09:32  王仲康  阅读(4)  评论(0)    收藏  举报