二分查找力扣题(leetcode)

二分查找力扣题(leetcode)

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

难度:困难

相关标签:高级工程师、数组二分查找分治

题目:

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

示例 1:

C++ 复制代码
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

C++ 复制代码
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

提示:

  • \(nums1.length == m\)
  • \(nums2.length == n\)
  • \(0 <= m <= 1000\)
  • \(0 <= n <= 1000\)
  • \(1 <= m + n <= 2000\)
  • \(-10^6 <= nums1[i], nums2[i] <= 10^6\)

代码:

C++ 复制代码
#include <vector>
using namespace std;

class Solution 
{
public:
    // 临时数组(足够容纳题目最大2000个元素)
    int tmp[2010];

    // 合并两个有序数组(核心修正:长度计算+返回完整合并结果)
    vector<int> merge_sort(int q1[], int l1, int r1, int q2[], int l2, int r2)
    {
        int i = l1, j = l2, k = 0;
        // 归并核心逻辑
        while(i <= r1 && j <= r2)
        {
            if(q1[i] <= q2[j])  tmp[k++] = q1[i++];
            else tmp[k++]  = q2[j++];
        }
        while(i <= r1)  tmp[k++] = q1[i++];
        while(j <= r2)  tmp[k++] = q2[j++];

        // 核心修正1:长度计算错误 → 正确计算总元素数
        int size = k; // k是实际合并的元素个数(最可靠)
        // 核心修正2:vector构造时用实际长度,不要-1
        vector<int> vec(tmp, tmp + size);

        return vec;
    }

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
    {
        // 处理空数组边界情况
        int* p1 = nums1.empty() ? nullptr : &nums1[0];
        int len1 = nums1.size();
        int* p2 = nums2.empty() ? nullptr : &nums2[0];
        int len2 = nums2.size();

        // 合并两个数组
        vector<int> mergeVec;
        if (len1 == 0) mergeVec = nums2; // nums1空,直接用nums2
        else if (len2 == 0)  mergeVec = nums1; // nums2空,直接用nums1
        else mergeVec = merge_sort(p1, 0, len1-1, p2, 0, len2-1);

        // 计算中位数
        int total = mergeVec.size();
        if (total % 2 == 1) 
            return (double)mergeVec[total / 2];// 奇数:取中间元素
        else 
        {
            // 偶数:取中间两个数的平均值
            int mid1 = mergeVec[total / 2 - 1];
            int mid2 = mergeVec[total / 2];
            return (mid1 + mid2) / 2.0;
        }
    }
};

// //模板
// int bSearch(int l, int r, int x)
// {
//     while(l < r)
//     {
//         int mid = (l+r) >> 1;
//         if(q[mid] >= x) r = mid;
//         else  l = mid + 1;
//     }
//     return l;
// }

// double bSearch(double l, double r, double x)
// {
//     const int eps = 1e-6;
//     while(l - r > eps)
//     {
//         if(check(mid) > x)  r = mid;
//         else  l = mid;
//     }
//     return l;
// }

    // void merge_sort(int q[], int l, int r)
    // {
    //     //终止条件
    //     if(l >= r)  return;

    //     //拆分成子问题
    //     int mid = (l+r)>>1;
        

    //     //递归处理子问题
    //     merge_sort(q, l, mid), merge_sort(q, mid+1, r);

    //     //合并子问题
    //     int i = l, j = mid+1, k = 0;
    //     while(i <= mid && j <= r)
    //     {
    //         if(q[i] <= q[j])  tmp[k++] = q[i++];
    //         else tmp[k++]  = q[j++];
    //     }

    //     while(i <= mid)  tmp[k++] = q[i++];
    //     while(j <= r)  tmp[k++] = q[j++];

    //     //复制回原数组
    //     for(int i = l, j = 0; i <= r; i++,j++)  q[i] = tmp[j];
    // }

相似题目

行排序矩阵的中位数中等

33. 搜索旋转排序数组

难度:中等

相关标签:数组二分查找

题目:

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

C++ 复制代码
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

C++ 复制代码
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

C++ 复制代码
输入:nums = [1], target = 0
输出:-1

提示:

  • \(1 <= nums.length <= 5000\)
  • \(-10^4 <= nums[i] <= 10^4\)
  • \(nums\) 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • \(-10^4 <= target <= 10^4\)

代码:

C++ 复制代码
class Solution 
{
public:
    int bSearch(vector<int>& nums, int l, int r, int x) 
    {
        while(l < r)
        {
            int mid = (l + r) >> 1; 
            if(nums[mid] >= x) r = mid;
            else  l = mid + 1;
        }
        // 循环结束后l=r,需要验证是否等于x(避免找不到的情况)
        return (nums[l] == x) ? l : -1;
    }

    int bSearchRotatePoint(vector<int>& nums, int l, int r) 
    {
        while(l < r)
        {
            int mid = (l + r) >> 1;
            // 旋转点在右段:mid比最后一个元素大,说明最小元素在mid右边
            if(nums[mid] > nums[r]) l = mid + 1;
            else r = mid; // 否则在mid或左边
        }
        return l;//循环结束l=r,就是旋转点
    }

    int search(vector<int>& nums, int target) 
    {
        int n = nums.size();
        if(n == 0) return -1;
        
        // 第一步:用二分找旋转点(最小元素的下标)
        int rotate_idx = bSearchRotatePoint(nums, 0, n-1); 
        
        // 第二步:分两段用你的模板找target
        if(target == nums[rotate_idx]) return rotate_idx; // 刚好是旋转点
        
        // 情况1:target在左段([0, rotate_idx-1])
        if(rotate_idx > 0 && target >= nums[0] && target <= nums[rotate_idx-1])
            return bSearch(nums, 0, rotate_idx - 1, target);
        // 情况2:target在右段([rotate_idx, n-1])
        else if(rotate_idx < n-1 && target >= nums[rotate_idx] && target <= nums[n-1])
            return bSearch(nums, rotate_idx, n - 1, target);
        // 情况3:只有一段(数组没旋转,或target不在数组中)
        else
        {
            // 数组没旋转时,直接用模板找整个数组
            if(rotate_idx == 0) return bSearch(nums, 0, n-1, target);
            // 否则target不存在
            else return -1;
        }
    }
};

相似题目

搜索旋转排序数组 II中等

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

通过倒水操作让所有的水桶所含水量相等中等

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

难度:中等

相关标签:数组二分查找

题目:

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

C++ 复制代码
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

C++ 复制代码
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

C++ 复制代码
输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • \(0 <= nums.length <= 10^5\)
  • \(-10^9 <= nums[i] <= 10^9\)
  • \(nums\) 是一个非递减数组
  • \(-10^9 <= target <= 10^9\)

代码:

C++ 复制代码
#include <vector>
using namespace std;

class Solution 
{
public:
    int bSearch(vector<int>& q, int l, int r, int x)
    {
        while (l < r) 
        {
            int mid = (l + r) >> 1;
            if (q[mid] >= x)  r = mid;
            else  l = mid + 1;
        }
        return (q[l] == x) ? l : -1;
    }

    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {
        // 边界1:空矩阵直接返回false
        if (matrix.empty() || matrix[0].empty()) return false;
        
        int m = matrix.size();    // 行数
        int n = matrix[0].size(); // 列数
        
        // 二分查找target
        for (int i = 0; i < m; i++) 
        {
            // 优化:如果当前行的第一个元素>target,后续行都更大,直接break
            if (matrix[i][0] > target) break;
            // 优化:如果当前行的最后一个元素<target,跳过当前行
            if (matrix[i][n-1] < target) continue;
            
            // 通过二分法,查找当前行是否有target
            int pos = bSearch(matrix[i], 0, n-1, target);
            if (pos != -1)  return true;// 找到target 
        }
        
        // 所有行都没找到
        return false;
    }
};

相似题目

第一个错误的版本简单

蜡烛之间的盘子中等

找出数组排序后的目标下标简单

240. 搜索二维矩阵 II

难度:中等

相关标签:数组二分查找

题目:

编写一个高效的算法来搜索 *m* x *n* 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例 1:

C++ 复制代码
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

C++ 复制代码
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

C++ 复制代码
输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • \(0 <= nums.length <= 10^5\)
  • \(-10^9 <= nums[i] <= 10^9\)
  • \(nums\) 是一个非递减数组
  • \(-10^9 <= target <= 10^9\)

代码:

C++ 复制代码
#include <vector>
using namespace std;

class Solution 
{
public:
    int bSearch(vector<int>& q, int l, int r, int x)
    {
        while (l < r) 
        {
            int mid = (l + r) >> 1;
            if (q[mid] >= x)  r = mid;
            else  l = mid + 1;
        }
        return (q[l] == x) ? l : -1;
    }

    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {
        // 边界1:空矩阵直接返回false
        if (matrix.empty() || matrix[0].empty()) return false;
        
        int m = matrix.size();    // 行数
        int n = matrix[0].size(); // 列数
        
        // 二分查找target
        for (int i = 0; i < m; i++) 
        {
            // 优化:如果当前行的第一个元素>target,后续行都更大,直接break
            if (matrix[i][0] > target) break;
            // 优化:如果当前行的最后一个元素<target,跳过当前行
            if (matrix[i][n-1] < target) continue;
            
            // 通过二分法,查找当前行是否有target
            int pos = bSearch(matrix[i], 0, n-1, target);
            if (pos != -1)  return true;// 找到target 
        }
        
        // 所有行都没找到
        return false;
    }
};

相似题目

第一个错误的版本简单

蜡烛之间的盘子中等

找出数组排序后的目标下标简单

287. 寻找重复数

难度:中等

相关标签:位运算数组双指针二分查找

题目:

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

C++ 复制代码
输入:nums = [1,3,4,2,2]
输出:2

示例 2:

C++ 复制代码
输入:nums = [3,1,3,4,2]
输出:3

示例 3:

C++ 复制代码
输入:nums = [3,3,3,3,3]
输出:3

提示:

  • \(0 <= nums.length <= 10^5\)
  • \(-10^9 <= nums[i] <= 10^9\)
  • \(nums\) 是一个非递减数组
  • \(-10^9 <= target <= 10^9\)

代码:

C++ 复制代码
class Solution
{
public:
    int bSearch(vector<int>& q, int l, int r)
    {
        while(l < r)
        {
            int mid = (l + r) >> 1;
            // 统计数组中 <= mid 的数的个数
            int cnt = 0;
            for(int num : q)
            {
                if(num <= mid) cnt++;
            }
            // 核心判断:cnt>mid说明重复数在左区间[1,mid]
            if(cnt > mid) r = mid;
            else l = mid + 1; // 否则在右区间[mid+1, r]
        }
        return l; // 最终收敛到重复数
    }

    int findDuplicate(vector<int>& nums)
    {
        // 数值范围是[1, nums.size()-1](因为数组长度是n+1,数值是1~n)
        return bSearch(nums, 1, nums.size() - 1);
    }
};

「核心公理」(解题的关键)

题目条件:数组有 n+1 个数,数值都在 [1, n] 之间,且只有一个数重复

👉 正常情况下(无重复):数值 ≤ mid 的数,最多有 mid 个。

👉 异常情况(有重复):如果数值 ≤ mid 的数 超过 mid ,说明重复数一定藏在 [1, mid] 里;反之则藏在 [mid+1, n] 里。

✅ 「升序的 1~n 无重复数组」是「≤mid 的数 = mid」的标准情况(也是最多的情况);

✅ 只要数组里有重复数,就会让某个 mid 对应的「≤mid 的数的个数」超过 mid ------ 这就是我们二分找重复数的核心依据。

相似题目

缺失的第一个正数困难

只出现一次的数字简单

环形链表 II中等

丢失的数字简单

错误的集合简单

300. 最长递增子序列

难度:中等

相关标签:数组二分查找动态规划

题目:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

C++ 复制代码
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

C++ 复制代码
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

C++ 复制代码
输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • \(1 <= nums.length <= 2500\)
  • \(-10^4 <= nums[i] <= 10^4\)

代码:

C++ 复制代码
class Solution 
{
public:
    // 复用你的二分模板:找数组q中[l, r]里第一个 >= x的位置
    int bSearch(vector<int>& q, int l, int r, int x) 
    {
        while(l < r)
        {
            int mid = (l + r) >> 1; // 和你的模板完全一致
            if(q[mid] >= x) r = mid;
            else  l = mid + 1;
        }
        return l; // 最终l=r,就是第一个>=x的位置
    }

    int lengthOfLIS(vector<int>& nums) 
    {
        if(nums.empty()) return 0;
        vector<int> tails; // 维护最长递增子序列的最小末尾数组

        for(int num : nums)
        {
            // 情况1:当前数比tails最后一个元素大,直接追加(延长子序列)
            if(tails.empty() || num > tails.back())
            {
                tails.push_back(num);
            }
            else// 情况2:用二分找第一个>=num的位置,替换它(贪心优化)
            {
                int pos = bSearch(tails, 0, tails.size()-1, num);
                tails[pos] = num;
            }
        }

        return tails.size(); // tails的长度就是最长递增子序列的长度
    }
};

相似题目

递增的三元子序列中等

俄罗斯套娃信封问题困难

最长数对链中等

最长递增子序列的个数中等

两个字符串的最小ASCII删除和中等

得到山形数组的最少删除次数困难

找出到每个位置为止最长的有效障碍赛跑路线困难

使数组 K 递增的最少操作次数困难

最长理想子序列中等

你能拿走的最大图书数量困难

最长递增子序列 II困难

求出最长好子序列 II困难

求出最长好子序列 I中等

找出有效子序列的最大长度 I中等

找出有效子序列的最大长度 II中等

最长相邻绝对差递减子序列中等

一步一步的实操演示 + 通俗解释 ,把「贪心 + 二分」的每一个操作落地到具体例子上,让你清楚看到这些方法是怎么 "用起来" 的 ------ 以示例 nums = [10,9,2,5,3,7,101,18] 为例,全程还原解题过程:

第一步:初始化核心数组

先创建空数组 tails,它的作用是「记录不同长度递增子序列的最小末尾」,初始时 tails = []

第二步:遍历数组,逐个处理每个数

我们按顺序处理 nums 中的每个数,每一步都严格执行「贪心 + 二分」规则:

处理第 1 个数:10
  • 当前 tails 为空 → 直接追加;
  • 操作:tails.push_back(10)tails = [10]
  • 含义:长度为 1 的最长递增子序列,最小末尾是 10。
处理第 2 个数:9
  • 规则判断:9 ≤ tails 最后一个元素(10)→ 触发二分;
  • 二分操作(找 tails 中第一个≥9 的位置):
    • 二分区间 l=0, r=0(tails 长度 1)→ 直接返回 pos=0;
  • 替换操作:tails[0] = 9tails = [9]
  • 含义:长度为 1 的最长递增子序列,最小末尾更新为 9(比 10 更小,后续更容易接数)。
处理第 3 个数:2
  • 规则判断:2 ≤ tails 最后一个元素(9)→ 触发二分;
  • 二分操作(找 tails 中第一个≥2 的位置):
    • 二分区间 l=0, r=0 → 返回 pos=0;
  • 替换操作:tails[0] = 2tails = [2]
  • 含义:长度为 1 的最长递增子序列,最小末尾更新为 2(最优)。
处理第 4 个数:5
  • 规则判断:5 > tails 最后一个元素(2)→ 直接追加;
  • 操作:tails.push_back(5)tails = [2,5]
  • 含义:
    • 长度 1 的最小末尾:2;
    • 长度 2 的最小末尾:5。
处理第 5 个数:3
  • 规则判断:3 ≤ tails 最后一个元素(5)→ 触发二分;
  • 二分操作(找 tails 中第一个≥3 的位置):
    • 二分区间 l=0, r=1
      1. mid=(0+1)>>1=0 → tails[0]=2 < 3 → l=1;
      2. 循环结束,返回 pos=1;
  • 替换操作:tails[1] = 3tails = [2,3]
  • 含义:长度 2 的最长递增子序列,最小末尾更新为 3(比 5 更小,后续更容易接数)。
处理第 6 个数:7
  • 规则判断:7 > tails 最后一个元素(3)→ 直接追加;
  • 操作:tails.push_back(7)tails = [2,3,7]
  • 含义:
    • 长度 1:2;长度 2:3;长度 3:7。
处理第 7 个数:101
  • 规则判断:101 > tails 最后一个元素(7)→ 直接追加;
  • 操作:tails.push_back(101)tails = [2,3,7,101]
  • 含义:长度 4 的最长递增子序列,最小末尾是 101。
处理第 8 个数:18
  • 规则判断:18 ≤ tails 最后一个元素(101)→ 触发二分;
  • 二分操作(找 tails 中第一个≥18 的位置):
    • 二分区间 l=0, r=3:
      1. mid=(0+3)>>1=1 → tails[1]=3 < 18 → l=2;
      2. mid=(2+3)>>1=2 → tails[2]=7 < 18 → l=3;
      3. 循环结束,返回 pos=3;
  • 替换操作:tails[3] = 18tails = [2,3,7,18]
  • 含义:长度 4 的最长递增子序列,最小末尾更新为 18(比 101 更优)。

第三步:最终结果

遍历结束后,tails 的长度是 4 → 这就是最长递增子序列的长度(和示例答案一致)。

关键方法的落地总结

方法 具体怎么用
贪心策略 tails 记录「不同长度子序列的最小末尾」,优先让末尾更小(为后续延长留空间)
追加规则 当前数 > tails 最后一个元素 → 直接 push_back,延长子序列长度
二分模板 当前数 ≤ tails 最后一个元素 → 调用模板找「第一个≥当前数的位置」,替换该位置的值
结果输出 最终 tails 的长度 = 最长递增子序列的长度

核心疑问解答(为什么这样做能得到正确长度?)

你可能会问:tails 里的元素不是真正的最长子序列(比如最终 tails 是 [2,3,7,18],而实际最长子序列是 [2,3,7,101]),为什么长度是对的?

→ 因为我们的目标是「求长度」,不是「找具体子序列」。tails 的核心作用是记录 "能达到的最长长度",替换操作只是优化末尾值,不影响长度的统计 ------ 只要能追加元素,就说明长度能 + 1,这就够了。