我相信大部分人都见过这么一个题目:“寻找任意一个一维数组中的局部最大值”,无论是在算法课上还是在 Leetcode 上,并为了理解讲解而抓耳挠腮。
笔者曾获得 ICPC 2020 世界总决赛资格,ICPC 2020 亚洲区域总决赛第五名。
峰值元素是指其值大于左右相邻值的元素。给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-peak-element
void solve(int l,int r)
{
int mid=l+r>>1;
if (nums[mid]>num[mid+1]) return solve(l,mid);
return solve(mid+1,r);
}
int main()
{
int ans=solve(0,n-1);
}
二维这个问题,笔者最早是在 Stanford 的某次算法课作业中看到:
int n,a[1010][1010];
int query(int x,int y)
{
if (a[x][y]>0) return a[x][y];
printf("? %d %d\n",x-1,y-1);
fflush(stdout);
int z;
scanf("%d",&z);
a[x][y]=z;
return z;
}
void solve(int rl,int rr,int cl,int cr)
{
if (rr==rl) {printf("! %d %d\n",rr-1,cr-1);return ;}
if (rr-rl==1)
{
int a=query(rl,cl),b=query(rl,cl+1),c=query(rl+1,cl),d=query(rl+1,cl+1);
if (a>b&&a>c) {printf("! %d %d\n",rl-1,cl-1);return ;}
if (b>a&&b>d) {printf("! %d %d\n",rl-1,cl);return ;}
if (c>a&&c>d) {printf("! %d %d\n",rl,cl-1);return ;}
printf("! %d %d\n",rl,cl);return ;
}
int rmid=rl+rr>>1,cmid=cl+cr>>1;
int Min=0,Minr,Minc,t;
for (int i=rl;i<=rr;i++)
{
t=query(i,cl);
if (t>Min) Min=t,Minr=i,Minc=cl;
t=query(i,cr);
if (t>Min) Min=t,Minr=i,Minc=cr;
t=query(i,cmid);
if (t>Min) Min=t,Minr=i,Minc=cmid;
}
for (int i=cl+1;i<cr;i++)
{
t=query(rl,i);
if (t>Min) Min=t,Minr=rl,Minc=i;
t=query(rr,i);
if (t>Min) Min=t,Minr=rr,Minc=i;
t=query(rmid,i);
if (t>Min) Min=t,Minr=rmid,Minc=i;
}
int wx=-1,wy,w=0;
if (Minr-1>=rl)
{
int t=query(Minr-1,Minc);
if (t>Min&&t>w) w=t,wx=Minr-1,wy=Minc;
}
if (Minr+1<=rr)
{
int t=query(Minr+1,Minc);
if (t>Min&&t>w) w=t,wx=Minr+1,wy=Minc;
}
if (Minc-1>=cl)
{
int t=query(Minr,Minc-1);
if (t>Min&&t>w) w=t,wx=Minr,wy=Minc-1;
}
if (Minc+1<=cr)
{
int t=query(Minr,Minc+1);
if (t>Min&&t>w) w=t,wx=Minr,wy=Minc+1;
}
if (wx==-1) {printf("! %d %d\n",Minr-1,Minc-1);return ;}
if (wx<rmid&&wy<cmid) solve(rl,rmid-1,cl,cmid-1);
else if (wx>rmid&&wy<cmid) solve(rmid+1,rr,cl,cmid-1);
else if (wx<rmid&&wy>cmid) solve(rl,rmid-1,cmid+1,cr);
else solve(rmid+1,rr,cmid+1,cr);
}
int main()
{
memset(a,0,sizeof a);
scanf("%d",&n);
solve(1,n,1,n);
fflush(stdout);
return 0;
}
这种解法的正确性已经在上面的证明中得到了保证,我们考虑这种解法的时间复杂度,显然首次需要询问 6n 个位置,而之后每一次子矩阵中均只需要询问中间行和中间列的元素(因为其周围的边界在上一次询问中已经完成,可以重复使用询问结果),所以询问次数的上限是 8n ,即使用此方法解决问题的时间复杂度是O(n)。