当前位置: 首页 > news >正文

【数组和二分查找】

一、数组核心知识点解构
1. 数组的内存存储模型

数组是连续内存空间上的相同类型数据集合,其物理结构决定了访问和操作特性:

  • 随机访问:通过下标直接定位元素,时间复杂度 (O(1))。

    int arr[5] = {1,2,3,4,5};
    int value = arr[3]; // 直接访问第4个元素(下标3),内存地址计算:base_address + 3 * sizeof(int)
    
  • 插入/删除操作:需移动后续元素,时间复杂度 (O(n))。

    原数组:[1,2,3,4,5] → 删除下标2的元素
    操作后:[1,2,4,5,_] → 元素4、5前移,空缺位置由垃圾值填充(逻辑删除)
    
  • 二维数组的内存布局

    • C++:按行优先存储,二维数组arr[m][n]可视为m个连续的一维数组。
      int arr[2][3] = {{0,1,2}, {3,4,5}};
      // 内存布局:0 1 2 3 4 5(行与行无缝衔接)
      
    • Java:二维数组是数组的数组,每行首地址不连续。
      int[][] arr = new int[2][3];
      // arr[0] 和 arr[1] 是两个独立的一维数组引用
      
2. 数组与动态数组对比
特性原生数组动态数组(如C++ vector)
内存分配静态分配(编译时确定大小)动态分配(运行时可扩容)
大小调整不可变支持resize()、push_back()
内存连续性完全连续扩容时可能重新分配内存
性能略高(无额外开销)略低(需维护容量信息)
二、二分查找算法深度解析
1. 算法本质:分治思想

二分查找通过每次将搜索区间减半,将时间复杂度优化至 (O(\log n))。其核心在于有序性区间定义的不变性

2. 循环不变量规则详解

区间定义决定了代码的边界处理方式,常见两种模式:

  • 左闭右闭区间 [left, right]

    • 循环条件:left <= right(区间包含两端点,left==right时仍有效)。
    • 边界收缩:若nums[middle] > target,更新right = middle - 1(排除middle)。
  • 左闭右开区间 [left, right)

    • 循环条件:left < right(区间不含rightleft==right时为空集)。
    • 边界收缩:若nums[middle] > target,更新right = middle(保留middle在下次搜索区间外)。

三、二分查找核心代码实现与对比

版本一:左闭右闭区间 [left, right]
// C++ 实现(ACM模式)
#include <iostream>
#include <vector>
using namespace std;int binarySearch(vector<int>& nums, int target) {int left = 0;                   // 左边界初始化为数组第一个元素下标int right = nums.size() - 1;    // 右边界初始化为数组最后一个元素下标(闭区间)while (left <= right) {         // 循环条件:区间内至少有一个元素int middle = left + (right - left) / 2;  // 防止整数溢出,等价于 (left + right) / 2if (nums[middle] == target) {return middle;          // 找到目标值,返回下标} else if (nums[middle] > target) {right = middle - 1;     // 目标在左半区,排除middle,右边界收缩到middle-1} else {left = middle + 1;      // 目标在右半区,排除middle,左边界扩张到middle+1}}return -1;                      // 未找到目标值
}int main() {int n, target;cin >> n >> target;             // 输入数组长度和目标值vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];             // 输入数组元素}cout << binarySearch(nums, target) << endl;return 0;
}
版本二:左闭右开区间 [left, right)
// C++ 实现(ACM模式)
int binarySearch(vector<int>& nums, int target) {int left = 0;                   // 左边界初始化为数组第一个元素下标int right = nums.size();        // 右边界初始化为数组长度(开区间,不包含此位置)while (left < right) {          // 循环条件:区间内至少有一个元素int middle = left + (right - left) / 2;  // 防止整数溢出if (nums[middle] == target) {return middle;          // 找到目标值,返回下标} else if (nums[middle] > target) {right = middle;         // 目标在左半区,右边界收缩到middle(不含middle)} else {left = middle + 1;      // 目标在右半区,左边界扩张到middle+1}}return -1;                      // 未找到目标值
}
Python实现对比
# 左闭右闭区间
def binary_search_closed(nums, target):left, right = 0, len(nums) - 1while left <= right:mid = left + (right - left) // 2  # 整数除法防止溢出if nums[mid] == target:return midelif nums[mid] > target:right = mid - 1else:left = mid + 1return -1# 左闭右开区间
def binary_search_open(nums, target):left, right = 0, len(nums)while left < right:mid = left + (right - left) // 2if nums[mid] == target:return midelif nums[mid] > target:right = midelse:left = mid + 1return -1

四、二分查找经典题型详解

1. 704. 二分查找(基础题)
  • 题意:在有序数组中查找目标值,返回下标或-1。
  • 思路:直接二分,两种区间均可。
  • 关键点
    • 数组有序且无重复元素,找到即返回。
    • 若使用左闭右开区间,注意右边界初始化为nums.size()
2. 35. 搜索插入位置
  • 题意:在有序数组中查找目标值,若存在返回下标;否则返回插入位置。
  • 思路
    • 二分查找过程中,若找到目标值,直接返回。
    • 若未找到,最终left即为插入位置(因为循环结束时,left左侧所有元素均小于目标值)。
  • 代码实现
    int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {return mid;           // 找到目标值,返回下标} else if (nums[mid] > target) {right = mid - 1;      // 目标在左半区} else {left = mid + 1;       // 目标在右半区}}return left;                  // 未找到时,left即为插入位置
    }
    
  • 图解
    数组:[1,3,5,6],目标值4
    初始:left=0, right=3
    第一次循环:mid=1, nums[1]=3 < 4 → left=2, right=3
    第二次循环:mid=2, nums[2]=5 > 4 → left=2, right=1
    循环结束:left=2,插入位置为2(即5的位置)
    
3. 34. 在排序数组中查找元素的第一个和最后一个位置
  • 题意:在有序数组中查找目标值的左右边界,若不存在返回[-1, -1]
  • 思路
    • 找左边界:调整二分条件,当nums[mid] >= target时收缩右边界,最终left为左边界。
    • 找右边界:当nums[mid] <= target时收缩左边界,最终right为右边界的下一个位置,减1得右边界。
  • 代码实现
    vector<int> searchRange(vector<int>& nums, int target) {int left = findLeft(nums, target);int right = findRight(nums, target);if (left <= right && nums[left] == target) {  // 验证存在性return {left, right};}return {-1, -1};
    }int findLeft(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] >= target) {  // 关键条件:>=时收缩右边界right = mid - 1;} else {left = mid + 1;}}return left;  // 返回左边界
    }int findRight(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] <= target) {  // 关键条件:<=时收缩左边界left = mid + 1;} else {right = mid - 1;}}return right;  // 返回右边界
    }
    
  • 关键点
    • 左边界函数中,即使找到目标值,仍继续向左收缩。
    • 右边界函数中,即使找到目标值,仍继续向右收缩。
    • 最终需验证left <= rightnums[left] == target,确保目标值存在。
4. 69. x 的平方根
  • 题意:计算非负整数x的平方根,结果向下取整。
  • 思路
    • 二分查找范围为[0, x],判断中点mid是否满足mid * mid <= x
    • 注意整数溢出,使用long类型存储平方值。
  • 代码实现
    int mySqrt(int x) {if (x == 0) return 0;  // 特殊处理0int left = 1, right = x;int res = 0;  // 记录可能的解while (left <= right) {long mid = left + (right - left) / 2;  // 防止溢出long square = mid * mid;if (square == x) {return mid;  // 精确解} else if (square < x) {res = mid;   // 记录当前可能的解left = mid + 1;  // 尝试更大的值} else {right = mid - 1;  // 尝试更小的值}}return res;  // 返回最大的满足条件的值
    }
    
  • 优化
    • 对于较大的x,可将右边界初始化为x/2(当x >= 2时,平方根不超过x/2)。
5. 367. 有效的完全平方数
  • 题意:判断正整数x是否为完全平方数。
  • 思路
    • 同69题,二分查找平方根,若找到精确解则返回true
  • 代码实现
    bool isPerfectSquare(int x) {if (x == 0) return true;int left = 1, right = x;while (left <= right) {long mid = left + (right - left) / 2;long square = mid * mid;if (square == x) {return true;  // 找到精确解} else if (square < x) {left = mid + 1;} else {right = mid - 1;}}return false;  // 未找到精确解
    }
    

五、二分查找进阶技巧与拓展

1. 二分查找的数学原理

二分查找每次将搜索区间减半,时间复杂度为 (O(\log n)),推导如下:

  • 第1次迭代:区间长度 (n)
  • 第2次迭代:区间长度 (n/2)
  • 第k次迭代:区间长度 (n/2^k)
  • 当区间长度为1时停止:(n/2^k = 1 \Rightarrow k = \log_2 n)
2. 二分查找的变种题型
  • 查找第一个大于等于目标值的元素
    int lower_bound(vector<int>& nums, int target) {int left = 0, right = nums.size();while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] >= target) {right = mid;} else {left = mid + 1;}}return left;
    }
    
  • 查找第一个大于目标值的元素
    int upper_bound(vector<int>& nums, int target) {int left = 0, right = nums.size();while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] > target) {right = mid;} else {left = mid + 1;}}return left;
    }
    
3. 二分查找的应用场景扩展
  • 最小值最大化问题:如LeetCode 410分割数组的最大值。
  • 最大值最小化问题:如LeetCode 875爱吃香蕉的珂珂。
  • 旋转排序数组:如LeetCode 33搜索旋转排序数组。

六、C++与Python库函数对比

1. C++标准库中的二分查找
  • lower_bound:返回首个不小于目标值的元素迭代器。
    vector<int> nums = {1,3,5,7};
    auto it = lower_bound(nums.begin(), nums.end(), 4);
    // it指向5,下标为distance(nums.begin(), it)
    
  • upper_bound:返回首个大于目标值的元素迭代器。
    auto it = upper_bound(nums.begin(), nums.end(), 5);
    // it指向7,下标为2
    
  • binary_search:判断元素是否存在。
    bool exists = binary_search(nums.begin(), nums.end(), 5);
    // 存在返回true,否则false
    
2. Python中的二分查找
  • bisect模块
    import bisect
    nums = [1, 3, 5, 7]# 查找左边界
    left = bisect.bisect_left(nums, 4)  # 返回2# 查找右边界的下一个位置
    right = bisect.bisect_right(nums, 5)  # 返回3# 插入元素保持有序
    bisect.insort(nums, 6)  # nums变为[1,3,5,6,7]
    

七、二分查找通用模板(终极版)

模板一:左闭右闭区间(找确定值)
int binarySearch(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {return mid;  // 找到目标值} else if (nums[mid] > target) {right = mid - 1;  // 左半区} else {left = mid + 1;  // 右半区}}return -1;  // 未找到
}
模板二:左闭右开区间(找边界)
// 找左边界:第一个大于等于target的位置
int lowerBound(vector<int>& nums, int target) {int left = 0, right = nums.size();while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] >= target) {right = mid;  // 收缩右边界} else {left = mid + 1;  // 扩张左边界}}return left;  // 返回左边界
}// 找右边界:第一个大于target的位置减1
int upperBound(vector<int>& nums, int target) {int left = 0, right = nums.size();while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] > target) {right = mid;  // 收缩右边界} else {left = mid + 1;  // 扩张左边界}}return left - 1;  // 返回右边界
}

八、常见错误与避坑指南

  1. 整数溢出

    • 错误写法:mid = (left + right) / 2
    • 正确写法:mid = left + (right - left) / 2
  2. 循环条件与区间定义不一致

    • 左闭右闭区间:循环条件应为left <= right
    • 左闭右开区间:循环条件应为left < right
  3. 边界处理错误

    • 找左边界时,若nums[mid] == target,应继续向左收缩。
    • 找右边界时,若nums[mid] == target,应继续向右收缩。
  4. 数组越界

    • 当使用左闭右开区间时,右边界初始化为nums.size(),但访问元素时需确保下标不越界。

九、总结与记忆要点

  1. 数组特性

    • 内存连续,支持随机访问,插入/删除需移动元素。
    • 二维数组在C++中连续存储,Java中为数组的数组。
  2. 二分条件

    • 数组有序,时间复杂度 (O(\log n))。
  3. 区间定义

    • 左闭右闭[left, right],循环left <= right,边界收缩right = mid - 1
    • 左闭右开[left, right),循环left < right,边界收缩right = mid
  4. 解题步骤

    1. 明确题目要求(找值、左边界、右边界)。
    2. 选择区间定义,确定循环条件。
    3. 处理中点比较逻辑,更新边界。
    4. 验证结果是否符合题意。
  5. 变种题型

    • 基础查找、插入位置、左右边界、平方根、完全平方数、旋转数组等。
  6. 库函数

    • C++:lower_boundupper_boundbinary_search
    • Python:bisect_leftbisect_right

通过以上系统化解析,可彻底掌握二分查找的核心思想与实现细节,应对各类面试题时游刃有余。建议结合LeetCode题目反复练习,形成肌肉记忆!
第二份:

二分查找经典题型详解(ACM模式)

1. 704. 二分查找(基础题)

题意:在升序数组中查找目标值,存在返回下标,否则返回-1。
思路:使用二分查找,分两种区间定义实现。

C++实现(左闭右闭区间 [left, right]
#include <iostream>
#include <vector>
using namespace std;// 二分查找:左闭右闭区间
int binarySearch(vector<int>& nums, int target) {int left = 0;                  // 左边界初始化为数组首个元素下标(闭区间)int right = nums.size() - 1;   // 右边界初始化为数组末元素下标(闭区间)while (left <= right) {        // 区间内至少有一个元素(left==right时仍有效)int mid = left + (right - left) / 2;  // 防溢出计算中点,等价于(left+right)/2if (nums[mid] == target) {return mid;          // 找到目标值,直接返回下标} else if (nums[mid] > target) {right = mid - 1;     // 目标在左半区,收缩右边界(排除mid)} else {left = mid + 1;      // 目标在右半区,扩张左边界(排除mid)}}return -1;                   // 未找到目标值
}int main() {int n, target;cin >> n >> target;          // 输入数组长度和目标值vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];          // 输入有序数组元素}cout << binarySearch(nums, target) << endl;return 0;
}
Python实现(左闭右开区间 [left, right)
def binary_search(nums, target):left = 0                   # 左边界初始化为数组首个元素下标(闭区间)right = len(nums)          # 右边界初始化为数组长度(开区间,不包含此位置)while left < right:        # 区间内至少有一个元素(left==right时区间为空)mid = left + (right - left) // 2  # 防溢出计算中点if nums[mid] == target:return mid         # 找到目标值,直接返回下标elif nums[mid] > target:right = mid        # 目标在左半区,收缩右边界(不含mid)else:left = mid + 1     # 目标在右半区,扩张左边界(含mid+1)return -1                  # 未找到目标值# ACM输入处理
import sys
data = list(map(int, sys.stdin.read().split()))
n = data[0]
target = data[1]
nums = data[2: 2+n]
print(binary_search(nums, target))

关键点

  • 区间定义:左闭右闭用<=,左闭右开用<
  • 中点计算:避免left+right溢出,用left + (right-left)/2
2. 35. 搜索插入位置

题意:在升序数组中查找目标值,存在返回下标;否则返回插入位置。
思路:二分查找,未找到时left为插入位置(左闭右闭区间)。

C++实现(左闭右闭区间)
#include <vector>
using namespace std;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) {right = mid - 1;       // 目标在左半区,收缩右边界} else {left = mid + 1;        // 目标在右半区,扩张左边界}}// 未找到时,left左侧元素均小于target,故left为插入位置return left;
}// ACM主函数(同704题,修改函数调用即可)
Python实现(左闭右闭区间)
def search_insert(nums, target):left, right = 0, len(nums) - 1  # 闭区间初始化while left <= right:mid = left + (right - left) // 2if nums[mid] == target:return midelif nums[mid] > target:right = mid - 1else:left = mid + 1return left  # 插入位置为left# ACM输入处理(同704题Python版)

关键点

  • 循环结束后,left是首个不小于target的元素下标,即插入位置。
3. 34. 查找元素的第一个和最后一个位置

题意:在升序数组中找目标值的左右边界,不存在返回[-1, -1]
思路

  • 找左边界:调整条件为nums[mid] >= target,循环结束后left为左边界。
  • 找右边界:调整条件为nums[mid] <= target,循环结束后right为右边界的下一个位置,减1得右边界。
C++实现
#include <vector>
using namespace std;// 找左边界:首个>=target的位置
int findLeft(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;  // 闭区间初始化while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] >= target) {          // 关键条件:>=时向左收缩right = mid - 1;} else {left = mid + 1;}}return left;  // 返回左边界(可能越界,需验证)
}// 找右边界:首个<=target的位置的下一个位置减1
int findRight(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;  // 闭区间初始化while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] <= target) {          // 关键条件:<=时向右收缩left = mid + 1;} else {right = mid - 1;}}return right;  // 返回右边界(可能越界,需验证)
}vector<int> searchRange(vector<int>& nums, int target) {int left = findLeft(nums, target);int right = findRight(nums, target);// 验证左边界是否越界且目标值存在if (left < nums.size() && nums[left] == target) {return {left, right};}return {-1, -1};
}// ACM主函数(输入数组和target,输出左右边界)
Python实现
def find_left(nums, target):left, right = 0, len(nums) - 1  # 闭区间初始化while left <= right:mid = left + (right - left) // 2if nums[mid] >= target:       # 向左收缩条件right = mid - 1else:left = mid + 1return leftdef find_right(nums, target):left, right = 0, len(nums) - 1  # 闭区间初始化while left <= right:mid = left + (right - left) // 2if nums[mid] <= target:       # 向右收缩条件left = mid + 1else:right = mid - 1return rightdef search_range(nums, target):left = find_left(nums, target)right = find_right(nums, target)if left <= len(nums)-1 and nums[left] == target:return [left, right]else:return [-1, -1]# ACM输入处理

关键点

  • 左边界函数中,即使nums[mid] == target,仍继续向左收缩以找到第一个出现的位置。
  • 右边界函数中,需验证left是否越界,避免访问无效下标。
4. 69. x 的平方根

题意:计算非负整数x的平方根,向下取整。
思路:二分查找范围[1, x],判断mid*mid <= x,用long防溢出。

C++实现
#include <iostream>
using namespace std;int mySqrt(int x) {if (x == 0) return 0;          // 特判x=0int left = 1, right = x;       // 查找范围[1, x]int res = 0;                   // 记录最大可行解while (left <= right) {        // 闭区间循环long mid = left + (right - left) / 2;  // 防溢出long square = mid * mid;if (square == x) {return mid;            // 精确解} else if (square < x) {res = mid;             // 记录当前解,尝试更大值left = mid + 1;} else {right = mid - 1;       // 尝试更小值}}return res;                    // 返回最大下界
}// ACM主函数(输入x,输出平方根)
int main() {int x;cin >> x;cout << mySqrt(x) << endl;return 0;
}
Python实现
def my_sqrt(x):if x == 0:return 0left, right = 1, x           # 查找范围[1, x]res = 0while left <= right:mid = left + (right - left) // 2square = mid * midif square == x:return midelif square < x:res = mid            # 记录当前解left = mid + 1else:right = mid - 1return res# ACM输入处理
x = int(input())
print(my_sqrt(x))

关键点

  • long存储mid*mid避免整数溢出(C++中int相乘可能溢出)。
  • 循环结束后res是最大的满足mid*mid <= x的值。
5. 367. 有效的完全平方数

题意:判断正整数x是否为完全平方数。
思路:同69题,找到平方根后验证平方是否等于x

C++实现
#include <iostream>
using namespace std;bool isPerfectSquare(int x) {if (x == 0) return true;       // 特判x=0int left = 1, right = x;       // 查找范围[1, x]while (left <= right) {long mid = left + (right - left) / 2;  // 防溢出long square = mid * mid;if (square == x) {return true;           // 找到精确解} else if (square < x) {left = mid + 1;        // 尝试更大值} else {right = mid - 1;       // 尝试更小值}}return false;                  // 未找到精确解
}// ACM主函数(输入x,输出是否为完全平方数)
int main() {int x;cin >> x;cout << (isPerfectSquare(x) ? "true" : "false") << endl;return 0;
}
Python实现
def is_perfect_square(x):if x == 0:return Trueleft, right = 1, xwhile left <= right:mid = left + (right - left) // 2square = mid * midif square == x:return Trueelif square < x:left = mid + 1else:right = mid - 1return False# ACM输入处理
x = int(input())
print(is_perfect_square(x))

二分查找规律抽象

  1. 适用条件

    • 数组有序(升序/降序,需明确比较方向)。
    • 需快速定位目标值或边界(时间复杂度 (O(\log n)))。
  2. 区间定义决定代码结构

    区间类型循环条件边界收缩逻辑(以升序数组为例)
    左闭右闭 [l,r]l <= rr = mid-1(当nums[mid] > target
    左闭右开 [l,r)l < rr = mid(当nums[mid] > target
  3. 边界处理通用逻辑

    • 找左边界:调整条件为>= target,循环结束后left为首个不小于target的位置。
    • 找右边界:调整条件为<= target,循环结束后right为首个大于target的位置减1。
  4. 防溢出技巧

    • 中点计算用mid = left + (right - left) / 2代替(left+right)/2
    • 涉及平方运算时用long类型(C++)或大整数(Python)。

快速记忆要点

  1. 二分三步曲

    • 定义区间(闭区间/开区间)。
    • 计算中点,比较中点值与目标值。
    • 根据区间定义更新边界。
  2. 关键变量含义

    • left:当前搜索区间的左边界(包含)。
    • right:当前搜索区间的右边界(闭区间包含,开区间不包含)。
    • mid:中点,用于分割区间。
  3. 题型变种处理

    • 插入位置:循环结束后left即为结果。
    • 左右边界:用>=<=调整条件,验证结果有效性。
    • 数值计算:注意类型溢出,用二分查找可行解区间。

通过“区间定义→条件判断→边界收缩”的固定逻辑,可应对各类二分查找问题。

二分查找拓展题型详解(ACM模式)

1. LeetCode 410. 分割数组的最大值(最小值最大化问题)

题意:将一个非负整数数组分割成m个子数组,求所有分割方式中子数组最大值的最小值
思路

  • 二分查找:目标是找到最小的最大值mid,范围为[max(nums), sum(nums)]
  • 验证函数:判断是否可以将数组分割成不超过m个子数组,且每个子数组的和不超过mid
C++实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;// 验证是否可以在m个子数组内,每个子数组和<=mid
bool check(vector<int>& nums, int m, int mid) {int cnt = 1;       // 子数组数量,至少1个long sum = 0;      // 防止溢出,用longfor (int num : nums) {if (num > mid) return false;  // 单个元素超过mid,直接失败if (sum + num > mid) {        // 当前子数组和超过mid,分割cnt++;sum = num;} else {sum += num;}}return cnt <= m;  // 子数组数量不超过m则可行
}int splitArray(vector<int>& nums, int m) {long left = *max_element(nums.begin(), nums.end());  // 左边界:数组最大值long right = accumulate(nums.begin(), nums.end(), 0L);  // 右边界:数组总和long res = right;  // 初始化为最大值while (left <= right) {  // 左闭右闭区间long mid = left + (right - left) / 2;if (check(nums, m, mid)) {  // 可行,尝试更小值res = mid;right = mid - 1;} else {  // 不可行,需要更大值left = mid + 1;}}return res;
}int main() {int n, m;cin >> n >> m;vector<int> nums(n);for (int i = 0; i < n; i++) cin >> nums[i];cout << splitArray(nums, m) << endl;return 0;
}
Python实现
def check(nums, m, mid):cnt = 1total = 0for num in nums:if num > mid:return Falseif total + num > mid:cnt += 1total = numelse:total += numreturn cnt <= mdef split_array(nums, m):left = max(nums)right = sum(nums)res = rightwhile left <= right:  # 闭区间mid = left + (right - left) // 2if check(nums, m, mid):res = midright = mid - 1else:left = mid + 1return res# ACM输入处理
import sys
data = list(map(int, sys.stdin.read().split()))
n = data[0]
m = data[1]
nums = data[2: 2+n]
print(split_array(nums, m))

关键点

  • 二分范围:最小值至少是数组中的最大值(单个元素无法分割),最大值是数组总和(不分割)。
  • 验证逻辑:贪心分割,尽可能让每个子数组的和接近mid,统计分割次数是否达标。
2. LeetCode 875. 爱吃香蕉的珂珂(最大值最小化问题)

题意:珂珂吃香蕉,每小时最多吃k根,求吃完所有香蕉的最小速度k,使得总时间不超过h小时。
思路

  • 二分查找k的范围是[1, max(nums)],验证k是否可行。
  • 计算时间:每堆香蕉需ceil(pile/k)小时,可用(pile + k - 1) // k计算。
C++实现
#include <iostream>
#include <vector>
using namespace std;// 验证速度k是否能在h小时内吃完
bool canEat(vector<int>& piles, int h, int k) {long time = 0;  // 防止溢出for (int pile : piles) {time += (pile + k - 1) / k;  // 向上取整if (time > h) return false;  // 超时}return true;
}int minEatingSpeed(vector<int>& piles, int h) {int left = 1;                  // 最小速度1int right = *max_element(piles.begin(), piles.end());  // 最大速度为最大堆int res = right;while (left <= right) {  // 闭区间int mid = left + (right - left) / 2;if (canEat(piles, h, mid)) {  // 可行,尝试更小速度res = mid;right = mid - 1;} else {  // 不可行,需要更大速度left = mid + 1;}}return res;
}int main() {int n, h;cin >> n >> h;vector<int> piles(n);for (int i = 0; i < n; i++) cin >> piles[i];cout << minEatingSpeed(piles, h) << endl;return 0;
}
Python实现
def can_eat(piles, h, k):time = 0for pile in piles:time += (pile + k - 1) // k  # 向上取整if time > h:return Falsereturn Truedef min_eating_speed(piles, h):left = 1right = max(piles)res = rightwhile left <= right:mid = left + (right - left) // 2if can_eat(piles, h, mid):res = midright = mid - 1else:left = mid + 1return res# ACM输入处理
import sys
data = list(map(int, sys.stdin.read().split()))
n = data[0]
h = data[1]
piles = data[2: 2+n]
print(min_eating_speed(piles, h))

关键点

  • 向上取整技巧(a + b - 1) // b等价于ceil(a/b)
  • 二分方向:可行解向左收缩,不可行解向右扩张,寻找最小可行k
3. LeetCode 33. 搜索旋转排序数组(旋转数组搜索)

题意:升序数组在某个点旋转后(如[0,1,2,4,5,6,7][4,5,6,7,0,1,2]),搜索目标值,存在返回下标,否则返回-1。
思路

  • 二分查找:判断中点所在的有序区间,确定目标值在左半区还是右半区。
  • 判断有序区间
    • nums[mid] >= nums[left],左半区有序。
    • 否则,右半区有序。
C++实现
#include <iostream>
#include <vector>
using namespace std;int search(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;  // 闭区间while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) return mid;  // 找到目标值// 判断左半区是否有序if (nums[mid] >= nums[left]) {// 左半区有序,判断target是否在左半区if (target >= nums[left] && target < nums[mid]) {right = mid - 1;  // 目标在左半区} else {left = mid + 1;  // 目标在右半区}} else {// 右半区有序,判断target是否在右半区if (target > nums[mid] && target <= nums[right]) {left = mid + 1;  // 目标在右半区} else {right = mid - 1;  // 目标在左半区}}}return -1;
}int main() {int n, target;cin >> n >> target;vector<int> nums(n);for (int i = 0; i < n; i++) cin >> nums[i];cout << search(nums, target) << endl;return 0;
}
Python实现
def search(nums, target):left, right = 0, len(nums) - 1  # 闭区间while left <= right:mid = left + (right - left) // 2if nums[mid] == target:return mid# 判断左半区是否有序if nums[mid] >= nums[left]:if nums[left] <= target < nums[mid]:right = mid - 1else:left = mid + 1else:# 右半区有序if nums[mid] < target <= nums[right]:left = mid + 1else:right = mid - 1return -1# ACM输入处理
import sys
data = list(map(int, sys.stdin.read().split()))
n = data[0]
target = data[1]
nums = data[2: 2+n]
print(search(nums, target))

关键点

  • 旋转数组性质:必定存在一个有序子数组,通过比较中点和左端点判断哪侧有序。
  • 边界条件:处理target等于端点的情况,确保区间判断正确。
4. LeetCode 153. 寻找旋转排序数组中的最小值(旋转数组找最小值)

题意:升序数组旋转后,寻找最小值(如[3,4,5,1,2]的最小值为1)。
思路

  • 二分查找:比较中点和右端点,确定最小值在左半区或右半区。
    • nums[mid] > nums[right],最小值在右半区。
    • 否则,最小值在左半区或就是nums[mid]
C++实现
#include <iostream>
#include <vector>
using namespace std;int findMin(vector<int>& nums) {int left = 0, right = nums.size() - 1;  // 闭区间while (left < right) {  // 循环至left==rightint mid = left + (right - left) / 2;if (nums[mid] > nums[right]) {// 最小值在右半区(mid右侧)left = mid + 1;} else {// 最小值在左半区或mid位置right = mid;}}return nums[left];  // 返回最小值
}int main() {int n;cin >> n;vector<int> nums(n);for (int i = 0; i < n; i++) cin >> nums[i];cout << findMin(nums) << endl;return 0;
}
Python实现
def find_min(nums):left, right = 0, len(nums) - 1  # 闭区间while left < right:  # 开区间思想,最终left==rightmid = left + (right - left) // 2if nums[mid] > nums[right]:left = mid + 1  # 最小值在右半区else:right = mid  # 最小值在左半区或midreturn nums[left]# ACM输入处理
n = int(input())
nums = list(map(int, input().split()))
print(find_min(nums))

关键点

  • 循环条件:使用left < right,最终left等于right时即为最小值。
  • 判断逻辑:若中点值大于右端点,说明旋转点在中点右侧,否则在左侧或中点本身。

二分查找规律再抽象

  1. 题型分类

    类型典型题目二分范围验证逻辑
    基础查找704. 二分查找[0, n-1]直接比较元素值
    边界查找34. 左右边界[0, n-1]调整条件为>=<=
    数值计算69. 平方根[1, x]判断平方值与x的大小关系
    最小值最大化410. 分割数组的最大值[max(nums), sum(nums)]贪心分割,统计子数组数量
    最大值最小化875. 爱吃香蕉的珂珂[1, max(nums)]计算时间是否达标
    旋转数组搜索33. 搜索旋转排序数组[0, n-1]判断有序区间,缩小搜索范围
    旋转数组找最小值153. 寻找旋转排序数组最小值[0, n-1]比较中点与右端点,确定最小值位置
  2. 通用步骤

    1. 确定二分范围:根据题意确定左边界和右边界(如数值范围、下标范围)。
    2. 定义区间类型:选择闭区间[left, right]或开区间[left, right),统一循环条件。
    3. 编写验证函数:判断当前中点是否可行,或确定目标值所在区间。
    4. 更新边界:根据验证结果收缩左边界或右边界,直至找到解。
  3. 关键技巧

    • 防溢出:中点计算用left + (right-left)/2,涉及大数用long(C++)或自动类型(Python)。
    • 旋转数组处理:通过比较中点与端点,判断有序区间,避免直接找旋转点。
    • 上下取整:最大值最小化问题中常用(a + b - 1) // b实现向上取整。

快速记忆要点(终极版)

  1. 二分条件

    • 数组有序、旋转有序、或可转化为有序的极值问题(如最小值最大化)。
  2. 区间模板

    • 闭区间left <= right,适用于找精确值或边界(如704、34)。
    • 开区间left < right,适用于逐步逼近解(如153)。
  3. 题型对应逻辑

    • 基础查找:直接比较,收缩边界。
    • 边界查找:用>=<=调整条件,验证结果有效性。
    • 极值问题:定义可行解区间,用贪心或数学方法验证可行性。
    • 旋转数组:利用有序子数组性质,缩小搜索范围。
  4. 代码框架

    while (left <= right) {mid = left + (right - left) / 2;if (条件成立) {收缩左边界或记录解;} else {收缩右边界;}
    }
    

通过“题型分类→区间定义→验证逻辑”的三层思维,可快速套用二分查找解决各类问题。建议结合题目练习,重点掌握旋转数组和极值问题的变种处理!

相关文章:

  • 鹰盾播放器禁止录屏操作的深度技术解析与全栈实现方案
  • 《高等数学》(同济大学·第7版)第三章第五节“函数的极值与最大值最小值“
  • SpringDoc集成到Springboot
  • 【PhysUnits】17.5 实现常量除法(div.rs)
  • git clone 时报错超时的问题解决方案
  • windows mysql zip部署
  • 国产 AI 绘画新标杆:HiDream-I1 的技术突破与创作
  • Python入门手册:常用的Python标准库
  • 企业中使用 MCP Server 实现业务打通
  • 全国大学生计算机应用能力与数字素养大赛 C语言程序设计赛项——本科组练习
  • 人工智能增强入侵检测系统以对抗高级持续性杀伤链
  • 《信号与系统》第 7 章 采样
  • 1.一起学习仓颉-编译环境,ide,输出hello,world
  • 鹰盾加密器基于AI的视频个性化压缩技术深度解析:从智能分析到无损压缩实践
  • Pytest断言全解析:掌握测试验证的核心艺术
  • Spring Boot 4.0.0 新特性详解:深入解读 Spring Framework 7.0.0
  • 通过Wrangler CLI在worker中创建数据库和表
  • 【群体结构 ADMIXTURE之一】: fast ancestry estimation
  • 闪回还能导致OGG同步失败
  • OpenLayers 可视化之热力图
  • 年度工作总结/seo排名怎么样
  • 个人网站 推荐/互联网营销顾问
  • 广州市技师学院/如何网站seo
  • 无锡高端网站建设/陕西优化疫情防控措施
  • 网页具有动画网站建设技术/青岛seo结算
  • 郴州市第四人民医院/网站seo优化方案策划书