文章为代码随想录的学习笔记,链接:
代码随想录
只要看到面试题中给出的数组是有序数组,都可以想一想是否可以使用二分法。
基本概念
二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。‘
查找过程:
- 从表的中间记录开始,如果给定值和中间记录的关键字相等,则查找成功。
- 如果给定值大于或小于中间记录的关键字,则在表中大于或小于中间记录的那一半中查找,重复操作,直到查找成功,或者在某一步中查找区间为空,则代表查找失败。
分别用low和high表示当前查找区间的下界和上界,mid为区间的中间位置。算法步骤:
1. 置查找区间初值,low为1,high为表长。
2 . 当low<=high时,循环执行以下操作:
- mid取low和high的中间值;
- 将给定值key与中间位置记录的关键字进行比较,若相等则查找成功,返回中间位置mid;
- 若不相等则利用中间位置将记录表对分成前、后两个子表。如果key比中间位置记录的关键字小,则high取mid-1,否则low取为mid+1。
3. 循环结束,说明查找区间为空,则查找失败,返回0。
LeetCode 704.二分查找
题目
题目链接:. - 力扣(LeetCode)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
提示:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
思路
这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
二分法不写乱的关键:区间
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
[left, right]
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
[left, right)
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
(博主水平有限,为了防止混淆只记[left, right]这种区间定义)
我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示:
题解
为了适应机试环境,先使用Dev C++编译器。对输入输出解释如下:
第一行输入:数组元素个数n target
第二行输入:数组nums
输出:元素下标
样例1:
输入:
6 9
-1 0 3 5 9 12输出:
4
样例2:
输入:
6 2
-1 0 3 5 9 12输出:
-1
#include <bits/stdc++.h>
using namespace std;
int search(vector<int>& nums, int target)
{
int left=0;
int right=nums.size()-1;
while(left<=right)
{
int middle=(left+right)/2;
if(nums[middle]<target)
{
left=middle+1; // target 在右区间,所以[middle + 1, right]
}
else if(nums[middle]>target)
{
right=middle-1;// target 在左区间,所以[left, middle - 1]
}
else return middle;
}
return -1;
}
int main()
{
int n,target,result;
cin>>n>>target;
vector<int> nums;
for(int i=0;i<n;i++)
{
int ans=0;
cin>>ans;
nums.push_back(ans);
}
result=search(nums,target);
cout<<result;
return 0;
}
注意:
vector容器只能使用push_back()对向量添加元素,下标只能用来获取已经存在的元素,so下面使用方法是错误的。
for(int i=0; i<10; i++){
a[i] = i; //应使用a.push_back(i)
}
在LeetCode中提交
复杂度分析
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
相关题目:LeetCode 35. 搜索插入位置
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。【暴力解法为n,说明必需使用二分法】
提示:
1 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
nums
为 无重复元素 的 升序 排列数组-10^4 <= target <= 10^4
思路
本题与704二分查找的区别在于如果目标值不存在于数组中,返回它将会被按顺序插入的位置。对题目可能出现的情况进行讨论:
- 目标值在数组所有元素之前
- 目标值等于数组中某一个元素
- 目标值插入数组中的位置
- 目标值在数组所有元素之后
归纳目标值不在原数组中的情况,发现可以返回left指针找到插入位置。
题解
为了适应机试环境,先使用Dev C++编译器。对输入输出解释如下:
第一行输入:数组元素个数n target
第二行输入:数组nums
输出:元素下标
样例1:
输入:
4 5
1 3 5 6
输出:
2
样例2:
输入:
4 2
1 3 5 6
输出:
1
样例3:
输入:
4 7
1 3 5 6
输出:
4
#include <bits/stdc++.h>
using namespace std;
int searchInsert(vector<int>& nums, int target)
{
int left=0;
int right=nums.size()-1;
while(left<=right)
{
int middle=(left+right)/2;
if(nums[middle]<target)
{
left=middle+1; // target 在右区间,所以[middle + 1, right]
}
else if(nums[middle]>target)
{
right=middle-1;// target 在左区间,所以[left, middle - 1]
}
else return middle;
}
return left;
}
int main()
{
int n,target,result;
cin>>n>>target;
vector<int> nums;
for(int i=0;i<n;i++)
{
int ans=0;
cin>>ans;
nums.push_back(ans);
}
result=searchInsert(nums,target);
cout<<result;
return 0;
}
在LeetCode中提交
复杂度分析
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
总结
对于题目中给出有序数组都可以考虑使用二分查找法。
使用二分查找法需要关注区间的定义,坚持循环不变量原则。
【区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。】