螺旋矩阵与螺旋队列

1|0螺旋矩阵

1|1方形矩阵

1|0问题描述

给定一个N×N的方阵,其中元素为自然数,排列规则为按照顺时针螺旋方向单调递增(起始值为1,终点值为N)。举例如下:

 若n = 3,螺旋矩阵为:

1 2 3 8 9 4 7 6 5

若n = 4,螺旋矩阵为:

1 2 3 4 12 13 14 5 11 16 15 6 10 9 8 7

若n = 5,螺旋矩阵为:

1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9

输入N

输出:螺旋矩阵N\timemsN

1|0解题思路

最简单直接的想法就是按照规则先申请好N\timesN的矩阵内存,然后按螺旋方向依次填入相应的元素,填充完毕后再双层循环打印出来。时间按复杂为O(n2),空间复杂度也为O(n2Leetcode 59

//N阶螺旋方阵 #include <stdio.h> #include <stdlib.h> void output(int **mat, int n) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { j == 0 || printf(" "); printf("%d", mat[i][j]); } printf("\n"); } } int main() { int n; scanf("%d", &n); int **mat = (int **)malloc(sizeof(int *) * n); for (int i = 0; i < n; i++) { mat[i] = (int *)malloc(sizeof(int) * n); } //初始化圆环边界 int rl = 0; int rh = n - 1; int cl = 0; int ch = n - 1; int now = 1; //圆环共有N/2 + 1个 while (1) { for (int j = cl; j <=ch; ++j) mat[rl][j] = now++; if (++rl > rh) break; for (int i = rl; i <= rh; ++i) mat[i][ch] = now++; if (--ch < cl) break; for (int j = ch; j >= cl; --j) mat[rh][j] = now++; if (--rh < rl) break; for (int i = rh; i >= rl; --i) mat[i][cl] = now++; if (++cl >ch) break; } output(mat, n);return 0; }

按照矩阵规律填充元素时,我们是随机访问矩阵元素的(如果可以按顺序访问,根本不用先存起来再打印)。随机访问内存,效率当然不高。所以即使时间复杂度已为最优,但那只是理论上的最优,在实践中表现并不一定就好。

假如能根据行列号直接计算出对应的矩阵元素就好了。N给定后,这个矩阵就已经唯一确定了,那么每一个元素也是确定的。也就是说,每一个位置放什么元素仅仅取决于N因此我们可以找到一个函数$element(i, j)ij$映射成对应这个行列号的元素。当然这个函数肯定不是一个简单的函数,不是一眼就可以看出来的,但也并不是不可能。

现在我们就来考查一下这个矩阵有什么特点。注意观察一下螺旋矩阵的最外层,它的左上角的元素是最小的,然后沿顺时针方向递增,就如同一个环一样(比如N为4时,1,2,...,12就是最外面一层环)。再往里面一层,也是一样,顺时针方向递增的一个环(比如N4时,13,14,15,16就是里面一层环)。以此类推,环里面还有一层环(N4时有2层环,N5时有3层环,最里面一层只有一个元素25),实际上是一个圆环套圆环结构。每一圆环最关键的元素就是左上角的那一个元素。只要知道了这个元素,再加上这个正方形环的边长就可以计算出剩下的元素。设左上角元素为pos,边长为len,以左上角为原(行号和列号均为0),其它元素的行号和列号都以它作参考,则element(i,j)的计算方法如下所示:

1. 若i==0element(i,j)=pos+j;

2. 否则若j==0element(i,j)=pos+4(len4)(i1)1;

3. 否则若i==len1element(i,j)=pos+4(len4)(len2)1j;

4. 否则element(i,j)=pos+len1+i;

详细的函数计算步骤见如下代码:

//顺序访问存储顺时针螺旋矩阵 //访问矩阵元素O(1) #include <stdio.h> #include <stdlib.h> #define min(a, b) ((a < b) ? (a) : (b)) #define max(a, b) ((a >= b) ? (a) : (b)) #define abs(a) ((a >= 0) ? (a) : (-a)) int calc(int n, int pos) { if (pos == 0) { return 1; } else { return 4 * (n - pos * 2 + 1) + calc(n, pos - 1); } } int element(int n, int i, int j) { int pos = min(min(i, n - 1 -i) , min(j, n - 1 - j)); int len = n - pos * 2; int base = calc(n, pos); //printf("pos = %d, base = %d, len = %d\n", pos, base, len); if ( i == pos) { return base + j - pos; } else if (j == pos) { return base + (len - 1) * 4 - i + pos; } else if (i == pos + len - 1) { return base + 3 * (len - 1) - j + pos; } else { return base + (len - 1) + i - pos; } } int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { j == 0 || printf(" "); printf("%d", element(n, i, j)); //element(n, i, j); } printf("\n"); } }

1|2一般矩阵NxM

打印一般矩阵与方阵的思路基本一致,以上代码稍微改动即可。下面讲讲其它常见的题型。

Leetcode54

1|0问题描述

输入:NM列矩阵;

输出:顺时针螺旋序列;

1|0解题思路

双百答案直接采用画圈思维,确定上下左右四个边界,画一条边就更新对应的边界,并做判断(是否画完)。

1|0代码实现(C)

int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize){ if (matrixSize == 0) { *returnSize = 0; return NULL; } * returnSize = matrixSize * (*matrixColSize); int *arr = (int *)malloc(sizeof(int) * (*returnSize)); int cl = 0; int ch = (*matrixColSize) - 1; int rl = 0; int rh = matrixSize - 1; int now = 0; while (1) { for (int j = cl; j <= ch; ++j) arr[now++] = matrix[rl][j]; if (++rl > rh) break; for (int i = rl; i <= rh; ++i) arr[now++] = matrix[i][ch]; if (--ch < cl) break; for (int j = ch; j >= cl; --j) arr[now++] = matrix[rh][j]; if (--rh < rl) break; for (int i = rh; i >= rl; --i) arr[now++] = matrix[i][cl]; if (++cl > ch) break; } return arr; }

【注意】这题有个坑!刚开始要检查输入数组是否为空(matrixSize == 0),不然一提交就会报错数组越界。

1|0Leetcode 885

1|0问题描述

在 R 行 C 列的矩阵上,我们从 (r0, c0) 面朝东面开始

这里,网格的西北角位于第一行第一列,网格的东南角位于最后一行最后一列。

现在,我们以顺时针按螺旋状行走,访问此网格中的每个位置。

每当我们移动到网格的边界之外时,我们会继续在网格之外行走(但稍后可能会返回到网格边界)。

最终,我们到过网格的所有 R * C 个空间。

按照访问顺序返回表示网格位置的坐标列表。

1|0 C语言渣题解

//Leetcode 885 int** spiralMatrixIII(int R, int C, int r0, int c0, int* returnSize, int** returnColumnSizes){ * returnSize = R * C; * returnColumnSizes = (int *)malloc(sizeof(int) * (*returnSize)); int **mat = (int **)malloc(sizeof(int *) * (*returnSize)); for (int i = 0; i < * returnSize; i++) { mat[i] = (int *)malloc(sizeof(int) * 2); (* returnColumnSizes)[i] = 2; } int cl = c0; int ch = c0; int rl = r0; int rh = r0; int now = 0; #define max(a, b) ((a) >= (b) ? (a) : (b)) #define min(a, b) ((a) <= (b) ? (a) : (b)) while (cl >= 0 || rl >= 0 || ch < C || rh < R) { if (ch < C) { for (int i = max(rl + 1, 0); i < min(R, rh); i++) { mat[now][0] = i; mat[now][1] = ch; now++; } } if (rh < R) { for (int j = min(C - 1, ch); j >= max(0, cl); --j) { mat[now][0] = rh; mat[now][1] = j; now++; } } if (cl >= 0 && cl < ch) { for (int i = min(R - 1, rh - 1); i >= max(0, rl + 1); --i) { mat[now][0] = i; mat[now][1] = cl; now++; } } if (rl >= 0 && rl < rh) { for (int j = max(0, cl); j <= min(C - 1, ch); ++j) { mat[now][0] = rl; mat[now][1] = j; now++; } } rl--; rh++; cl--; ch++; } return mat; }

2|0螺旋队列

螺旋队列长这样:

不难发现他的排列规律主要有两点:

  1. 顺时针螺旋,自然数递增
  2. 右斜上对角线数字(紫色)为奇数平方向上递增,且值与所在圈数有关

2|1问题描述

设1的坐标是(0,0),x方向向右为正,y方向向下为正,如图所示:

例如,7的坐标为(-1,-1),2的坐标为(1,0)。编程实现输入任意一点坐标(x,y),输出所对应的数字。

2|2解题思路

与螺旋矩阵类似的核心思路:画圈=>找到每一圈的基准点=>四条边上的元素相对于起始点的位置关系推出值的关系。

具体说来,设圈数从0开始编号。则有

  1. 不难看出紫色元素为所在圈最大值max,将它最为本圈的基准点(坐标为(r,r))。紫色元素取值为12,32,52,72,,与圈数编号r的关系:max=(2r+1)2。本圈(方形)边长为len=2r+1,则易得本圈最小值即它的正下方元素min=max4(2r+11)+1=(2r1)2+1
  2. 得到基准点元素值后,四条边的元素值对应坐标的关系就很容易得到了:

上边:top=maxr+x;

左边:left=max3ry;

下边:bottom=max5rx;

右边:right=max7r+y;

1|0代码实现

#include <stdio.h> #include <stdlib.h> #define max(a, b) ((a) >= (b) ? (a) : (b)) #define abs(a) ((a) >= (0) ? (a) : (-a)) int findvalue(int x, int y) { int r = max(abs(x), abs(y)); int max = (2 * r + 1) * (2 * r + 1); //printf("x = %d, y = %d\nr = %d, max = %d\n", x, y, r, max); if ( y == -r) { return max - r + x; } else if ( x == -r) { return max - 3 * r - y; } else if ( y == r) { return max - 5 * r - x; } else if ( x == r) { return max - 7 * r + y; } } int main() { int x , y; printf("请输入目标元素的坐标(x, y),以空格分隔:\n"); fflush(stdout); scanf("%d%d", &x, &y); fflush(stdout); printf("\n目标元素值为:%d, 下面输出相应的螺旋队列检验:\n", findvalue(x, y)); for (int j = -y; j <= y; j++) { for (int i = -x; i <= x; i++) { i == -x || printf(" "); printf("%-5d", findvalue(i, j)); } printf("\n"); } fflush(stdout); return 0; }

【注意】

  • 输出的矩阵坐标和题目给定的平面直角坐标系不同,两者相当于行列互换了!
  • %-5d控制输出对齐,fflush(stdout)清空缓冲区,保证第一个输出在输入之前显示。

 参考资料:

https://blog.csdn.net/songzitea/article/details/8348243

https://blog.csdn.net/Chen_Swan/article/details/105799956

https://www.it610.com/article/1543831.htm

它的正下方一位为本圈最小值min


__EOF__

本文作者Min
本文链接https://www.cnblogs.com/MinPage/p/13942418.html
关于博主:Information is everything
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   箐茗  阅读(421)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
     
点击右上角即可分享
微信分享提示