【代码随想录】二分查找

文章为代码随想录的学习笔记,链接:

代码随想录

只要看到面试题中给出的数组是有序数组,都可以想一想是否可以使用二分法。

基本概念

二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。‘

查找过程:

  • 从表的中间记录开始,如果给定值和中间记录的关键字相等,则查找成功。
  • 如果给定值大于或小于中间记录的关键字,则在表中大于或小于中间记录的那一半中查找,重复操作,直到查找成功,或者在某一步中查找区间为空,则代表查找失败。

分别用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)

总结

对于题目中给出有序数组都可以考虑使用二分查找法。

使用二分查找法需要关注区间的定义,坚持循环不变量原则。

【区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值