This time your job is to fill a sequence of N positive integers into a spiral matrix in non-increasing order. A spiral matrix is filled in from the first element at the upper-left corner, then move in a clockwise spiral. The matrix has m rows and n columns, where m and n satisfy the following: m×n must be equal to N; m≥n; and m−n is the minimum of all the possible values.
Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N. Then the next line contains N positive integers to be filled into the spiral matrix. All the numbers are no more than 104. The numbers in a line are separated by spaces.
Output Specification:
For each test case, output the resulting matrix in m lines, each contains n numbers. There must be exactly 1 space between two adjacent numbers, and no extra space at the end of each line.
Sample Input:
12
37 76 20 98 76 42 53 95 60 81 58 93
Sample Output:
98 95 93
42 37 81
53 20 76
58 60 76
题意理解
给定有N个整数的乱序序列,要求以非递增的顺序按照顺时针螺旋矩阵的形式输出(见解题思路中图示)。其中行数m要求要大于或等于列数n(m x n = N),并且行数要取得最小值。
方法一
数据结构
考虑通过行标和列标确定排好序的数组下标,所以序列只需要存储在一维数组中,由于数组长度可变,用vector容器实现很方便
解题思路
- 将N个正整数存入vector容器实现的数组并用sort实现非递增排序
- 通过对正整数N因数分解得到行数m和列数n
- for循环内i从0到N-1,通过i来确定行标row和列标col。
行和列能够唯一确定当前输出数字在有序数组中的下标。考虑每一个数字的序号为所有外层的数字个数之和与当前所在层数序号相加。如图在这组56个数据中,白框中的6,外面有两层,用黄线标识,6所在的当前层用蓝线标识。按照顺时针螺旋方向看,白框中的数字6所在的序列号为外层所有数据的个数(26+18 = 44)加上在当前层的序号即为6。
这里的44即为outSum, 6 即为inSum。
由图易知,最外层的数据个数outerCircleNum可求,即为2*(m+n)-4。外层圈数circleNum为列标和行标离最近边界之差。每向内推进一层,每层数据的个数-8,所以outSum可以用一个首项为outerCircleNum,公差为-8,n为圈数circleNum的等差数列求和求得。inSum也可以根据该数据在内圈的坐落情况分类讨论求得。
void getOutInNum(int row, int col, int &outNum, int &inNum){
int innerRowNum, innerColNum;//最内层圈的行数和列数
int innerRow, innerCol;//内层行标和列标
int tempMinVertical = getMin(row-1, m-row);
int tempMinHorizontal = getMin(col-1, n-col);
int circleNum = getMin(tempMinVertical, tempMinHorizontal);//点的外层圈数
int outerCircleNum = 2*(m+n)-4;//最外层数据个数
outNum = circleNum* outerCircleNum - circleNum*(circleNum-1)*8/2;//等差数列求和,公差为-8
innerColNum = n - circleNum*2;
innerRowNum = m - circleNum*2;
innerRow = row - circleNum;
innerCol = col - circleNum;
if(innerRow == 1)
inNum = innerCol;
else if(innerCol == innerColNum)
inNum = innerColNum + innerRow -1;
else if(innerRow == innerRowNum)
inNum = innerColNum + innerRowNum-1 + innerColNum-innerCol;
else if(innerCol == 1){
inNum = 2*innerColNum + innerRowNum - 2 + innerRowNum - innerRow;
}
}
注意事项
- sqrt函数输入和输出都是double型变量
- vector实现的数组时从0开始存储的,需要从0开始遍历
- 等差数列求和公差为-8
完整代码
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
int N, m, n;
int cmp(int a , int b){
return (a > b);
}
int getMin(int a , int b){
if (b < a)
a = b;
return a;
}
void getOutInNum(int row, int col, int &outNum, int &inNum){
int innerRowNum, innerColNum;//最内层圈的行数和列数
int innerRow, innerCol;//内层行标和列标
int tempMinVertical = getMin(row-1, m-row);
int tempMinHorizontal = getMin(col-1, n-col);
int circleNum = getMin(tempMinVertical, tempMinHorizontal);//点的外层圈数
int outerCircleNum = 2*(m+n)-4;//最外层数据个数
outNum = circleNum* outerCircleNum - circleNum*(circleNum-1)*8/2;//等差数列求和,公差为-8
innerColNum = n - circleNum*2;
innerRowNum = m - circleNum*2;
innerRow = row - circleNum;
innerCol = col - circleNum;
if(innerRow == 1)
inNum = innerCol;
else if(innerCol == innerColNum)
inNum = innerColNum + innerRow -1;
else if(innerRow == innerRowNum)
inNum = innerColNum + innerRowNum-1 + innerColNum-innerCol;
else if(innerCol == 1){
inNum = 2*innerColNum + innerRowNum - 2 + innerRowNum - innerRow;
}
}
int main()
{
scanf("%d",& N);
for(n = sqrt((double)N); n >= 1; n--){
if(N % n == 0){
m = N/n;
break;
}
}
vector <int> v;
v.resize(N);
int num;
for(int i = 1; i <= N; i++){
scanf("%d", &num);
v.push_back(num);
}
sort(v.begin(), v.end(), cmp);
int row, col;
int outNum, inNum;
int index;//二维点阵中每一个点应该输出的数字的下标
for(int i = 0; i <= N-1; i++){
row = i/n+1;
col = i%n+1;
if(col != 1)printf(" ");
getOutInNum(row,col,outNum,inNum);
index = outNum + inNum;
printf("%d", v[index-1]);//注意v下标从0开始
if(col == n && i != N)printf("\n");
}
return 0;
}
方法二<from 柳婼,添加注释>
数据结构
用vector<vector > 实现可变长二维数组
解题思路
参考了柳婼代码,实现思路和我的方法截然不同。
先将所有数据按照螺旋矩阵顺序存入二维数组,再直接输出。先计算⾏行行数m和列列数n的值,n从根号N的整数部分开始,往前推⼀一直到1,找到第⼀一个满⾜足N %
n== 0的,m的值等于N/n~将N个给定的值输⼊入数组a,并将a数组中的值按⾮非递增排序,接着建⽴立m⾏行行
n列列的数组b,填充时按层数填充,⼀一个包裹矩阵的⼝口字型为⼀一层,计算螺旋矩阵的层数level,如果m
的值为偶数,层数为m/2,如果m为奇数,层数为m/2+1,所以level = m / 2 + m % 2;因为是从左上⻆角
第1个格⼦子开始,按顺时针螺旋⽅方向填充,所以外层for循环控制层数i从0到level,内层for循环按左上到
右上、右上到右下、右下到左下、左下到左上的顺序⼀一层层填充
注意事项
- 可变二维数组的定义方法:
vector<vector<int> >b(m, vector<int>(n));
右边’>’ '>'中间有空格。二维数组名后面的括号内指定行数和每一行中元素的数据类型。
- 注意内层for循环中还要控制t <= N –1,因为如果螺旋矩阵中所有的元素已经都填充完毕,就不不能再重复填充<from 柳婼>
完整代码
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
int cmp(int a, int b){return a > b;}
int main(){
int N, m, n, t = 0;//t为递减容器的下标变量
scanf("%d", &N);
for(n = sqrt((double)N); n >= 1; n--){
if(N % n == 0){
m = N / n;
break;
}
}
vector<int>a(N);
for(int i = 0; i < N; i++)
scanf("%d",&a[i]);
sort(a.begin(),a.end(),cmp);
vector<vector<int> >b(m, vector<int>(n));//二维可变长容器的定义方法
int level = (m+1)/2;
for(int i = 0; i < level; i++){//层数从零开始 //注意每个里层for循环里要随时判断容器是否遍历完毕
for(int j = i; j <= n-1-i && t <= N-1; j++){//j为列标向右移动
b[i][j] = a[t++];
}
for(int j = i+1; j <= m-1-(i+1)&& t <= N-1; j++){//j为行标向下移动
b[j][n-1-i] = a[t++];
}
for(int j = n-1-i; j >= i && t <= N-1 ; j--){//j为列标向左移动
b[m-1-i][j] = a[t++];
}
for(int j = m-1-(i+1); j>= i+1 && t <= N-1; j--){//j为行标向上移动
b[j][i] = a[t++];
}
}
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(j!=0) printf(" ");
printf("%d", b[i][j]);
}
printf("\n");
}
return 0;
}
总结
这类题目属于简单模拟题,实现思路不难想到,但是要快速实现需要熟练的代码能力和仔细处理边界的能力。方法一实现逻辑简单,但是代码写起来偏啰嗦。方法二实现逻辑偏难,如果能想到的话,码量略少,适于考场。