1.二分查找简介
二分查找算法(Binary Search)是一种高效的查找算法,适用于有序数组或序列。它的基本思想是通过逐步缩小查找范围,将查找区间一分为二,直到找到目标值或确定目标值不存在。
算法原理:在数组的中间位置选择一个元素作为“中间值”。将目标值与“中间值”进行比较:如果目标值等于中间值,查找成功,返回中间值的索引。如果目标值小于中间值,则在左半部分继续查找。如果目标值大于中间值,则在右半部分继续查找。重复以上步骤,直到找到目标值或区间为空(即找不到目标值)。
时间复杂度:二分查找的时间复杂度为 (O(\log n)),其中 (n) 是数组的元素个数。每次查找将查找范围缩小一半,因而效率较高。
在二分查找中,针对特定需求,常用的有以下三种方式:
(1)朴素二分查找:查找任意一个等于目标值的位置。
朴素二分查找模版
while (left <= right)//循环条件
{int mid = left + (right - left) / 2;//left + (right - left + 1) / 2也可if (...) left = mid + 1;else if (...) right = mid - 1;elsereturn ...}
(2)左端点二分查找:查找目标值的最左位置(即第一个出现的目标值)。
二分查找区间左端点
while (left < right)//判断条件不能等于
{int mid = left + (right - left) / 2;//只能是left + (right - left) / 2if (...) left = mid + 1;else right = mid;
}
(3)右端点二分查找:查找目标值的最右位置(即最后一个出现的目标值)。
二分查找区间右端点
//找右端点while (left < right)//判断条件不能等于{int mid = left + (right - left + 1) / 2;//只能是left + (right - left + 1) / 2if (...) left = mid;else right = mid - 1;}
二分查找不仅适用于有序数组,还适用于具有二段性的情况。二段性指的是一个序列被划分成两段:在某个位置之前满足某个条件,而在该位置之后不满足(或反之)。利用二分查找,能够高效地找到这个转折点。
2.二分查找
1. 题目简介
2. 算法思想
暴力解法:遍历数组寻找目标值
二分查找:数组是升序的
(1)循环结束条件:left <= right
(2)中间值小于目标值,去右区间查找:mid < target -> left = mid + 1
中间值大于目标值,去左区间查找:mid > target -> right = mid - 1
中间值等于目标值:返回
3. 代码实现
class Solution {
public: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) left = mid + 1;else if (nums[mid] > target) right = mid - 1;else if (nums[mid] == target) return mid;}return -1;}
};
. - 力扣(LeetCode)
3.在排序数组中查找元素的第⼀个和最后⼀个位置
1. 题目简介
2. 算法思想
暴力解法:遍历数组,找到符合条件的区间
二分查找:有序数组,可以用二分查找,使用左右端点二分查找找到符合条件的区间的左右端点
(1)循环结束条件:left < right
(2)找左端点
中间值小于目标值,找大:mid < target -> left = mid + 1
中间值大于目标值,找小:mid >= target -> right = mid
(3)找右端点
中间值小于目标值,找大:mid <= target -> left = mid
中间值大于目标值,找小:mid > target -> right = mid - 1
3. 代码实现
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0) return {-1, -1};int begin = 0;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;}if (nums[right] != target) return {-1, -1};else begin = left;right = nums.size() - 1; //找右端点while (left < right){int mid = left + (right - left + 1) / 2;if (nums[mid] <= target) left = mid;else right = mid - 1;}return {begin, right};}
};
. - 力扣(LeetCode)
4. x的平安根
1. 题目简介
2. 算法思想
暴力解法:从1开始计算其平方,找到第一个平方大于target的数
二分查找:从1开始到x区间使用二分查找法,返回值左区间的元素平方小于等于x,右区间元素平方大于x。
(1)循环结束条件:left < right
(2)小于目标值,找大:mid * mid <= x -> left = mid
大于目标值,找小:mid * mid > x -> right = mid - 1
3. 代码实现
class Solution {
public:int mySqrt(int x) {if (x < 1) return 0;int left = 1, right = x;while (left < right){long long mid = left + (right - left + 1) / 2;if (mid * mid <= x) left = mid;else right = mid - 1;}return left;}
};
. - 力扣(LeetCode)
5. 搜索插入位置
1. 题目简介
2. 算法思想
暴力解法:遍历数组找到第一个比目标值大的元素的下标
二分查找:数组是升序的,我们要找的返回值是大于等于target的,因此将数组分为左右两个区间,左边区间小于target,右边区间大于等于target,,所以我们要找到右区间的左端点。
(1)循环结束条件:left < right
(2)中间值小于目标值,则应该找大:mid < target -> left = mid + 1
中间值大于目标值,找小:mid > target -> right = mid
class Solution {
public: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) left = mid + 1;else right = mid; }if (nums[left] < target) return left + 1;else return left;}
};
. - 力扣(LeetCode)
6. 山脉数组的封顶索引
1. 题目简介
暴力解法:遍历数组寻找峰值
二分查找:数组具有二段性,arr[i] < arr[i + 1],arr[i] > arr[i+ 1]
(1)循环结束条件:left < right
(2)处于升序部分:arr[mid] >= arr[mid - 1] left = mid;
处于降序部分:arr[mid] < arr[mid - 1] right = mid - 1;
3. 代码实现
class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {int left = 1, right = arr.size() - 2;while (left < right){int mid = left + (right - left + 1) / 2;if (arr[mid] >= arr[mid - 1]) left = mid;else right = mid - 1;}return left;}
};
. - 力扣(LeetCode)
7. 寻找峰值
1. 题目简介
2. 算法思想
3. 代码实现
暴力解法:遍历数组,找到一个大于左右相邻值的元素
二分查找算法:找二段性,nums[-1] = nums[n] = -∞,arr[i] > arr[i + 1] 则左边一定存在峰值,
arr[i] < arr[i + 1] 则右边一定存在峰值。
(1)循环结束条件: left < right
(2)处于递减序列中:nums[mid] > nums[mid + 1] -> right = mid;
处于递增序列中:nums[mid] <= nums[mid + 1] -> left = mid + 1
class Solution {
public:int findPeakElement(vector<int>& nums) {int left = 0, right = nums.size() - 1;while (left < right){long long mid = left + (right - left) / 2;if (nums[mid] > nums[mid + 1]) right = mid;else left = mid + 1;}return right;}
};
8.搜索旋转排序数组中的最小值
1. 题目简介
2. 算法思想
暴力解法:遍历数组找到最小值
二分查找:找二段性,AB之间大于数组最后一个元素,CD之间小于等于D元素
(1)循环条件:left < right
(2)中间值大于D:left = mid + 1
中间值小于等于D:right = mid
3. 代码实现
class Solution {
public:int findMin(vector<int>& nums) {int n = nums.size();int left = 0, right = n - 1;while (left < right){int mid = left + (right - left) / 2;if (nums[mid] > nums[n - 1]) left = mid + 1;else right = mid;}return nums[left];}
};
. - 力扣(LeetCode)
9. 0〜n-1 中缺失的数字
1. 题目简介
2. 算法思想
暴力解法:遍历数组,时间复杂度为O(n)
二分查找:找二段性,目标元素左边区间的元素和它们的下标相等,右边区间的元素比他们的下标大1
(1)循环结束条件:left < right
(2) 元素和下标相等:records[mid] == mid -> left = mid + 1;
不相等:right = mid;
3. 代码实现
class Solution {
public:int takeAttendance(vector<int>& records) {int left = 0, right = records.size() - 1;while (left < right){int mid = left + (right - left) / 2;if (records[mid] == mid) left = mid + 1;else right = mid;}if (records[left] == left) return left + 1;else return left;}
};
. - 力扣(LeetCode)