1 简介与实例
在有序数组中查找某一数据所在的下标,若存在该数据,返回下标,否则返回-1。
#include <cstdio>
int a[10]={1,3,5,7,9,11,13,15,17,19};
int value; //待查找元素的值
int index; //查找到的下标,如果没有查到,返回-1
int search_index(int a[],int n)
{
int l=0;
int r=n-1;
while(l<=r)
{
int mid=l+((r-l)>>2);
if(a[mid]==value)
return mid;
else if(a[mid]>value)
r=mid-1;
else
l=mid+1;
}
return -1;
}
int main()
{
printf("请输入您要查询的数据:\n");
while(scanf("%d",&value)!=EOF)
{
index=search_index(a,10);
printf("您所查询的数据%d所在的下标为:%d\n",value,index);
}
return 0;
}
以上是左闭右闭的写法,所谓左开右闭就是在[0,n-1]的区间查找,区间的左右边界都可以取到。
#include <cstdio>
int a[10]={1,3,5,7,9,11,13,15,17,19};
int value; //待查找元素的值
int index; //查找到的下标,如果没有查到,返回-1
int search_index(int a[],int n)
{
int l=0;
int r=n;
while(l<r)
{
int mid=l+((r-l)>>2);
if(a[mid]==value)
return mid;
else if(a[mid]>value)
r=mid;
else
l=mid+1;
}
return -1;
}
int main()
{
printf("请输入您要查询的数据:\n");
while(scanf("%d",&value)!=EOF)
{
index=search_index(a,10);
printf("您所查询的数据%d所在的下标为:%d\n",value,index);
}
return 0;
}
以上是左闭右开的写法,即在[0,n)的范围查找,右边的数字取不到。注意这种写法与左闭右闭写法的边界调整的区别。
2 二分的应用,变种与案例
二分查找常见的应用场景:
1 在有序序列中查找一个数,比如上边的例子。
2 并不一定非要在有序序列中得到应用,任意一个序列,只要在二分之后可以淘汰掉一半,那么就可以采用二分法。比如腾讯秋招笔试题目-贪吃的小Q
二分查找常见的考察点:
1 对于边界条件的考察以及代码实现的能力。
2 二分搜索常见题目的变化:
(1)给定处理或查找的对象不同。比如在有重复数值的数组中查找和在无重复数值的数组中查找时,处理情况的细节稍显差异。
(2)判断条件不同。一般需要查找一个数组中等于X的位置,我们也可以查找数组中大于或小于X的位置。
(3)要求返回的内容不同。返回任意一个等于X的位置,返回最早等于X的位置,或者返回等于X的个数等。
3 在有序循环数组中进行二分查找:
比如数组12345循环之后可以是:12345 23451 34512 45123 51234
二分搜索的重要提醒:
在选择中间节点时,选择的公式为mid=left+(right-left)/2, 防止数据过大时数据溢出。
案例一
本题可以用二分法解决。
1 如果数组为空或长度为0,返回-1,表示局部最小位置不存在。
2 如果数组长度为1,返回0,因为0便是局部最小位置。
3 如果数组长度大于1,先检查2头的位置。即:
(1)如果a[0]<a[1],返回0
(2)如果a[n-1]<a[n-2],返回n-1
4 如果2头不满足,说明a[0]>a[1],a[n-1]>a[n-2],所以从最左的位置向右看,有减小的趋势,从最右的位置向左看,也是减小的趋势。此时查看mid位置的数据:
(1)如果Mid位置的数据既比左边小,又比右边小,则为局部最小值。
(2)如果Mid的数据比右边小,比左边大,则mid往左是减小的趋势。取左部分继续查找。
(3)如果Mid的数据比左边小,比右边大,则Mid往右是减小的趋势,取右部分继续查找。
(4)如果mid的数据比左右都大,任选一侧都可以。
#include <cstdio>
const int maxn=20;
int n;
int a[maxn];
int index;
int find_part_min_index(int a[],int n)
{
if(n<=0)
return -1;
else if(n==1)
return 0;
else if(a[0]<a[1])
return 0;
else if(a[n-1]<a[n-2])
return n-1;
int left=0;
int right=n-1;
while(left<=right)
{
int mid=left+(right-left)>>1;
if(a[mid]<a[mid-1]&&a[mid]<a[mid+1])
return mid;
else if(a[mid]>a[mid-1]&&a[mid]<a[mid+1])
right=mid-1;
else if(a[mid]>a[mid+1]&&a[mid]<a[mid-1])
left=mid+1;
else
left=mid+1;
}
return -1;
}
int main()
{
while(true)
{
printf("请输入数组的元素个数:\n");
scanf("%d",&n);
printf("请输入数组的元素:\n");
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
index=find_part_min_index(a,n);
printf("数组的局部最小位置下标为:%d\n",index);
printf("\n");
}
return 0;
}
案例二
给定一个有序数组arr,再给定一个整数num,请在数组中找到数字num这个数出现的最左边的位置。
思路:定义一个全局变量ans,用变量记录数据num最后一次出现的位置。如果最后没有找到,就返回-1.
#include <cstdio>
const int maxn=20;
int ans;
int a[maxn];
int n;
int num=3;
int find_num_of_min_index(int a[],int n)
{
if(n<=0)
return -1;
int left=0;
int right=n-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(a[mid]==num)
{
ans=mid;
right=mid-1;
continue;
}
else if(a[mid]>num)
{
right=mid-1;
}
else
left=mid+1;
}
if(a[ans]==num)
return ans;
else
return -1;
}
int main()
{
while(true)
{
printf("请输入数组的元素个数:\n");
scanf("%d",&n);
printf("请输入数组的元素:\n");
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
int index=find_num_of_min_index(a,n);
printf("数组的关于数据%d的最小位置下标为:%d\n",num,index);
printf("\n");
}
return 0;
}
案例三
给定一个有序循环数组,返回数组中的最小值。{3,4,5,1,2}
首先判断a[0]是否小于a[n-1],如果小于,说明数列已经有序,直接返回a[0]即可。
否则,a[0]>=a[n-1]:说明是逆序的:
(1)如果a[0]>a[M],则在0-m的范围找;(注意不是m-1)
(2)如果a[n-1]<a[m] ,则在m+1----n-1的范围去找
(3)否则,必有a[0]<=a[m],a[m]<=a[n-1],又a[0]>=a[n-1],则a[0]=a[n-1]=a[m]。这个时候的情况就如:2222222221222。只可以通过遍历来确定了。
#include <cstdio>
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int solve(vector<int> &vec)
{
if(vec.size()==0)
return -1;
if(vec.size()==1)
return vec[0];
if(vec[0]<vec[vec.size()-1])
return vec[0];
int left=0;
int right=vec.size()-1;
while(left<=right)
{
int mid=(right-left)/2+left;
if(vec[left]>vec[mid])
right=mid;
else if(vec[mid]>vec[right])
left=mid+1;
else
{
int ans=vec[0];
for(int i=1;i<vec.size()-1;i++)
{
if(ans>vec[i])
ans=vec[i];
}
return ans;
}
}
return -1;
}
int main()
{
string input;
getline(cin,input);
istringstream istr(input);
int num;
vector<int> vec;
while(istr>>num)
{
vec.push_back(num);
}
int res=solve(vec);
if(res!=-1)
cout<<"数组中最小元素值为: "<<res<<endl;
else
cout<<"该数组为空数组!"<<endl;
return 0;
}
剑指offer思路:
如果中间值大于等于左边,令left为中间值下表,中间值在左边递增子序列
如果中间值小于等于右边,令right为中间值下标,中间值在右边递增子序列。
如果2个下标分属于2个子序列且下标差了1,则后面那个下标对应的值就是最小值。
但是如果昨边=右边=中间值,只可以遍历,因为无法得知中间值属于哪一个子序列。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int len = rotateArray.size();
if(len == 1) return rotateArray[0];
int left = 0,right = len-1;
if(rotateArray[left]<rotateArray[right]) return rotateArray[left];
while(rotateArray[left]>=rotateArray[right]){
if(right-left==1) return rotateArray[right];
int midIndex = left + (right-left)/2;
if(rotateArray[left]==rotateArray[midIndex]&&rotateArray[midIndex]==rotateArray[right]){
int minval = rotateArray[left];
int index = left + 1;
while(index<=right){
if(minval>rotateArray[index]) minval=rotateArray[index];
index++;
}
return minval;
}
if(rotateArray[midIndex]>=rotateArray[left]) left = midIndex;
else if(rotateArray[midIndex]<=rotateArray[right]) right = midIndex;
}
return -1;
}
};
案例四
给定一个有序数组arr,其中不含有重复元素,请找出满足arr[i]==i的条件最左的位置,如果所有位置上的数都不满足,返回-1。
思路如下:
(1)首先判断a[0]>=N-1,一定不存在。
(2)再判断a[N-1]<=0,同样不存在。
(3)观察中间元素:如果a[mid]>mid,right=mid-1;如果a[mid]<mid,left=mid+1;否则,记录该位置,继续right=mid-1;
#include <cstdio>
int res=-1; //记录最后出现的位置
const int maxn=100;
int a[maxn];
int find_index(int a[],int n)
{
if(n<=0)
return -1;
if(n==1)
{
if(a[0]==0)
return 0;
else
return -1;
}
if(a[0]>=n-1)
return -1;
if(a[n-1]<=0)
return -1;
int left=0;
int right=n-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(a[mid]>mid)
{
right=mid-1;
continue;
}
else if(a[mid]>mid)
{
left=mid+1;
continue;
}
else
{
res=mid;
right=mid-1;
}
}
return res;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
res=find_index(a,n);
printf("%d\n\n",res);
}
return 0;
}
案例五
给定一颗完全二叉树头节点head,返回这棵树节点的个数,若完全二叉树节点数为N,请实现时间复杂度低于O(N)的解法。
首先找到二叉树最左边的节点,目的是统计出二叉树的高度/深度。
接着,找到二叉树头节点的右子树的最左边节点,如果该节点可以到达最后一层,说明头节点左子树是一个满二叉树。
这时,求出左子树节点个数加上根节点,右子树节点个数可以通过递归求取。
如果该节点不可以到达最后一层,说明头节点的右子树是一颗完全二叉树。这时候求右子树节点个数加上根节点,左子树节点个数可以通过递归求得。
其时间复杂度为O(logN^2)
参考博客:Leetcode 222:完全二叉树的节点个数(最详细的解法!!!)_coordinate的博客-CSDN博客_二叉树第n层最多几个节点
案例六
如何更快地求取一个整数k的N次方。若2个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。
法一:
N为偶数
N为奇数
递归法:
#include <cstdio>
int base,exp;
int PowerWithExponent(int base,unsigned int exp)
{
if(exp==0)
return 1;
if(exp==1)
return base;
int result=PowerWithExponent(base,exp>>1);
result*=result;
if(exp&0x1==1)
result*=base;
return result;
}
int main()
{
printf("请输入底数和指数:\n");
scanf("%d%d",&base,&exp);
int result=PowerWithExponent(base,exp);
printf("%d\n",result);
return 0;
}
法二:
例如求,先把75写作二进制形式:1001011
即
那么我们可以先求出。再根据二进制相应位置是否为1选择性地进行乘法。
代码如下:
题目为
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
LL binaryPow(LL a, LL b,LL m)
{
LL res=1;
while(b>0)
{
if(b&1)
{
res=res*a%m;
}
a=a*a%m;
b>>=1;
}
return res;
}
int main()
{
LL a,b,m,res;
cin>>a>>b>>m;
res=binaryPow(a,b,m);
cout<<res<<endl;
return 0;
}
案例七 查找数组中第一个大于等于给定值的元素
#include <cstdio>
#include <iostream>
using namespace std;
int find_upper(int a[],int n)
{
if(n==0)
return -1;
if(n==1)
{
if(a[0]>=3)
return a[0];
else
return -1;
}
int left=0;
int right=n-1;
int res=-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(a[mid]<3)
left=mid+1;
else if(a[mid]>=3)
{
right=mid-1;
res=a[mid];
}
}
return res;
}
int main()
{
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int num=find_upper(a,n);
cout<<num;
}
案例八 求数值的近似值
求根号2的近似值,精度为1e-5
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const double eps=1e-5;
double f(double m)
{
return m*m;
}
int main()
{
double left=1;
double right=2;
double mid;
while(right-left>eps)
{
mid=(left+right)/2;
if(f(mid)>2)
right=mid;
else
left=mid;
}
cout<<mid<<endl;
return 0;
}
案例九 秋招猿辅导三面面试题:
一个二维蛇形矩阵,数值非递减,求一个数据在不在数组中。
1 2 3
6 5 4
7 9 11
思路:把二维看作一维,注意下标到真实位置的换算奥
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
bool invector(vector<vector<int> > &vt,int val)
{
if(vt.size()==0)
return false;
int n=vt.size();
int m=vt[0].size();
int left=0;
int right=n*m-1;
while(left<=right)
{
int mid=left+(right-left)/2;
//奇数行,递减
if(mid/m%2)
{
int data=vt[mid/m][m-mid%m-1];
if(val==data)
return true;
else if(val>data)
{
left=mid+1;
}
else
right=mid-1;
}
//偶数行,递增
else
{
int data=vt[mid/m][mid%m];
if(val==data)
return true;
else if(val>data)
{
left=mid+1;
}
else
right=mid-1;
}
}
return false;
}
int main()
{
int n,m,val;
cin>>n>>m>>val;
vector<vector<int> > vt;
for(int i=0;i<n;i++)
{
vector<int> temp(m,0);
for(int j=0;j<m;j++)
{
cin>>temp[j];
}
vt.push_back(temp);
}
if(invector(vt,val))
{
cout<<val<<"在vt中"<<endl;
}
else
cout<<val<<"不在vt中"<<endl;
return 0;
}
案例十 数字在升序数组中出现的次数
class Solution {
public:
int biSearch(vector<int> &data,float k){
int left = 0,right = data.size()-1;
while(left<=right){
int mid = left+(right-left)/2;
if(data[mid]>k) right = mid-1;
else left = mid+1;
}
return left;
}
int GetNumberOfK(vector<int> data ,int k) {
if(data.empty()) return 0;
if(data.size()==1&&data[0] == k) return 1;
if(data.size()==1&&data[0]!=k) return 0;
return biSearch(data,k+0.5)-biSearch(data,k-0.5);
}
};