螺旋矩阵,两步进阶,从暴力到o(1)
题目描述
一个 n 行 n 列的螺旋矩阵可由如下方法生成:
从矩阵的左上角(第 1 行第 1 列)出发,初始时向右移动;如果前方是未曾经过的格子,则继续前进,否则右转;重复上述操作直至经过矩阵中所有格子。根据经过顺序,在格子中依次填入 1, 2, 3, ... , n ,便构成了一个螺旋矩阵。
下图是一个 n = 4 时的螺旋矩阵。
| 1 | 2 |
3 | 4 |
| 12 | 13 | 14 | 5 |
| 11 | 16 | 15 | 6 |
| 10 | 9 | 8 | 7 |
现给出矩阵大小 n 以及 i 和 j ,请你求出该矩阵中第 i 行第 j 列的数是多少。
输入描述:
输入共一行,包含三个整数 n,i,j ,每两个整数之间用一个空格隔开,分别表示矩阵大小、待求的数所在的行号和列号。
输出描述:
输出一个整数,表示相应矩阵中第 i 行第 j 列的数。
输入
4 2 3
输出
14
备注:
对于 50% 的数据, 1 ≤ n ≤ 100 ;
对于 100% 的数据, 1 ≤ n ≤ 30,000,1 ≤ i ≤ n,1 ≤ j ≤ n
链接:https://ac.nowcoder.com/acm/problem/16502
来源:牛客网
1.暴力模拟法,o(n*2),只能通过50%的数据。
#include<iostream>
using namespace std;
/***********************
1<=n<=30000无法用数组存储
***********************/
int n,i,j;
int top_bound,bottom_bound,left_bound,right_bound;
int count=1;
int direction=0;
int main(){
cin>>n>>i>>j;
int k=1,m=1;
top_bound=1;
bottom_bound=n+1;
left_bound=0;
right_bound=n+1;
while(count<=n*n){
switch(direction){
case 0:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(m==right_bound){
//cout<<"reach right_bound "<<right_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
right_bound--;
m--;
k++;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
m++;
count++;
break;
case 1:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(k==bottom_bound){
//cout<<"reach bottom_bound "<<bottom_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
bottom_bound--;
k--;
m--;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
k++;
count++;
break;
case 2:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(m==left_bound){
//cout<<"reach left_bound "<<left_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
left_bound++;
m++;
k--;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
m--;
count++;
break;
case 3:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(k==top_bound){
//cout<<"reach top_bound "<<top_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
top_bound++;
k++;
m++;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
k--;
count++;
break;
}
}
return 0;
}
2. 外圈优化法,o(n),可通过100%的数据。
| 1 | 2 | 3 | 4 |
| 12 | 13 | 14 | 5 |
| 11 | 16 | 15 | 6 |
| 10 | 9 | 8 | 7 |
假设想求得矩阵(2,3)位置上的值,可以从13-14-15-16这一内圈的开始点13作为锚点(anchor),模拟螺旋的方式计算出(2,3)的数。
13=4^2-2^2+1,即anchor= n*n-(inner_n*inner_n)+1,其中inner_n=n-2*dist,dist是内圈到外圈的距离;
anchor可以直接根据公式计算出,该算法主要的时间复杂度为模拟螺旋的时间,即数内圈的大小,o(inner_n)~o(n)
#include<iostream>
using namespace std;
/***************************
1. 1 ≤ n ≤ 30000,因此无法将整个矩阵存储下来,只能输出目标结果
2. 模拟整个过程仍会超时,可将情况分为3类
(1)n==1, 输出1
(2)i,j==1|n,即该数在旋转矩阵的外围计算旋转矩阵边界上的值
(3)中间的数,根据首先计算外围数的个数,在外圈的数的基础上进行模拟
****************************/
int n,i,j;
int anchor=1,dist,anchor_pos=1;
int min(int x,int y,int z,int q){
return min(min(min(x,y),z),q);
}
int min(int x,int y){
return x>y?y:x;
}
int main(){
cin>>n>>i>>j;
if(n==1){
cout<<1<<endl;
}else{
int left,right,top,bottom;
left=j-1;
right=n-j;
top=i-1;
bottom=n-i;
//cout<<left<<" "<<right<<" "<<top<<" "<<bottom<<endl;
dist=min(left,right,top,bottom);
anchor_pos=dist+1;
anchor=n*n-(n-2*dist)*(n-2*dist)+1;
//cout<<dist<<" "<<anchor<<endl;
//接下来进行模拟,可在4*inner_n-4次计算中找到目标(i,j)
int k=anchor_pos,m=anchor_pos; //当前位置
int direction=0; //当前方向
int top_bound=dist+1;
int bottom_bound=n-dist+1;
int left_bound=dist;
int right_bound=n-dist+1;
int count=anchor;
while(count<=n*n){
switch(direction){
case 0:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(m==right_bound){
//cout<<"reach right_bound "<<right_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
right_bound--;
m--;
k++;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
m++;
count++;
break;
case 1:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(k==bottom_bound){
//cout<<"reach bottom_bound "<<bottom_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
bottom_bound--;
k--;
m--;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
k++;
count++;
break;
case 2:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(m==left_bound){
//cout<<"reach left_bound "<<left_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
left_bound++;
m++;
k--;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
m--;
count++;
break;
case 3:
//cout<<"from "<<k<<" "<<m<<" steps to "<<"direction "<<direction<<endl;
if(k==top_bound){
//cout<<"reach top_bound "<<top_bound<<" change direction from "<<direction<<" ";
direction=(direction+1)%4;
top_bound++;
k++;
m++;
//cout<<"to "<<direction<<endl;
continue;
}
if(k==i&&m==j){
cout<<count<<endl;
return 0;
}
k--;
count++;
break;
}
}
}
return 0;
}
3. 外圈+内圈优化,0(1)之路
| 1 | 2 | 3 | 4 |
| 12 | 12+1 | 12+2 | 5 |
| 11 | 12+4 | 12+3 | 6 |
| 10 | 9 | 8 | 7 |
| 1 | 2 |
| 4 | 3 |
从图中可以看出内圈减去外圈的最大值后,变成了最简单的一圈。
外圈的最大值记为anchor=n^2-inner_n^2。
通过观察规律可以发现
如果(i,j)在内圈的上部或右部,(i,j)在内圈的位置为inner_i+inner_j-1
如果在内圈的左部或下部,(i,j)在内圈的位置为4*inner_n-inner_i-inner_j-1
(i,j)在整个矩阵中的位置为外圈的最大值+内圈的位置。
该算法使得结果可以由公式直接计算出,它的时间复杂度为常数o(1)。
#include<iostream>
using namespace std;
int n,i,j;
int anchor,dist;
int left,right,top,bottom;
int min(int x,int y,int z,int q){
return min(min(min(x,y),z),q);
}
int min(int x,int y){
return x>y?y:x;
}
int f(){
int inner_n =n-2*dist;
int inner_i =i-dist;
int inner_j =j-dist;
//cout<<inner_n<<" "<<inner_i<<" "<<inner_j<<" "<<endl;
if(i-1==dist||n-j==dist){
return inner_i+inner_j-1;
}
return 4*inner_n-inner_i-inner_j-1;
}
int main(){
cin>>n>>i>>j;
if(n==1){
cout<<1<<endl;
}else{
int left,right,top,bottom;
left=j-1;
right=n-j;
top=i-1;
bottom=n-i;
dist=min(left,right,top,bottom);
anchor=n*n-(n-2*dist)*(n-2*dist);
//cout<<dist<<" "<<anchor<<endl;
int ans = anchor+f();
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号