赞
踩
疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。
他发明了一种写法:给出数字个数n
和行数m
(0 < n < 999,0 < m < 999)
,从左上角的1
开始,按照顺时针螺旋向内写方式,依次写出2, 3, ..., n
,最终形成一个m
行矩阵。
小明对这个矩阵有些要求:
*
号占位两个整数,空格隔开,依次表示n
、m
符合要求的唯一短阵
9 4
1 2 3
* * 4
9 * 5
8 7 6
注意,本题和LeetCode54、螺旋矩阵非常类似。
本题既可以用迭代方法也可以用递归方法完成。本题解主要介绍递归方法。
题目要求矩阵列数尽可能少,很容易算出来最小的列数为c = ceil(n / m)
。
注意到每次填充都优先填充矩阵的最外圈,比如对于5*5
的矩阵,初始化均用*
填充
* * * * *
* * * * *
* * * * *
* * * * *
* * * * *
假设需要填充21
个数,那么第一圈会螺旋式地填充如下
1 2 3 4 5
16 * * * 6
15 * * * 7
14 * * * 8
13 12 11 10 9
中间剩余一个(5-2)*(5-2) = 3*3
的未填充矩阵,那么可以从17
开始继续螺旋式地填充第二层矩阵,直到到达21
,即
1 2 3 4 5
16 17 18 19 6
15 * * 20 7
14 * * 21 8
13 12 11 10 9
显然每一次填充,都是从未填充矩阵的左上角开始填充其外层,直到填充完毕或到达规定数字。因此可以使用递归来完成上述过程。
# 题目:【模拟】2023C-螺旋矩阵 # 分值:100 # 作者:许老师-闭着眼睛学数理化 # 算法:模拟/递归 # 代码看不懂的地方,请直接在群上提问 from math import ceil # 递归函数 # ans为答案螺旋矩阵 # start_i, end_i, start_j, end_j分别为子矩阵的起始、终止的i和j下标 # cur_num为未填充子矩阵左上角的第一个数 # n为终止数字 def help(ans, start_i, end_i, start_j, end_j, cur_num, n): # 如果不存在遍历区间,则不会进入下面四个for循环中的任意一个 # 会持续进行递归,导致编译栈溢出 # 这种情况就是当n为某个奇数的平方且c = m = sqrt(n)的时候会出现 # 譬如填充5*5的矩阵且n = 25,最中间那个数字会出现的情况 # 额外判断这种条件,将最中间的数字填充上并退出递归即可 if start_i == end_i and start_j == end_j: ans[start_i][start_j] = cur_num return # 未填充矩阵的上边界:从左往右,固定start_i,正序遍历j for j in range(start_j, end_j): ans[start_i][j] = str(cur_num) cur_num += 1 if cur_num > n: return # 未填充矩阵的右边界:从上往下,固定end_j,正序遍历i for i in range(start_i, end_i): ans[i][end_j] = str(cur_num) cur_num += 1 if cur_num > n: return # 未填充矩阵的下边界:从右往左,固定end_i,逆序遍历j for j in range(end_j, start_j, -1): ans[end_i][j] = str(cur_num) cur_num += 1 if cur_num > n: return # 未填充矩阵的做边界:从下往上,固定start_j,逆序遍历j for i in range(end_i, start_i, -1): ans[i][start_j] = str(cur_num) cur_num += 1 if cur_num > n: return # 对未填充数组进行递归 # start_i, end_i, start_j, end_j需要修改 help(ans, start_i+1, end_i-1, start_j+1, end_j-1, cur_num, n) # 输入数字n,行数m n, m = map(int, input().split()) # 计算列数c,n除以m后向上取整 c = ceil(n / m) # 初始化答案螺旋数组,先用"*"填充 ans = [["*"] * c for _ in range(m)] # 递归入口:start_i, end_i, start_j, end_j # 分别传入0, m-1, 0, c-1 help(ans, 0, m-1, 0, c-1, 1, n) # 逐行输出螺旋矩阵 for row in ans: print(*row)
import java.util.Scanner; public class Main { // 递归函数 // ans为答案螺旋矩阵 // start_i, end_i, start_j, end_j分别为子矩阵的起始、终止的i和j下标 // cur_num为未填充子矩阵左上角的第一个数 // n为终止数字 static void help(String[][] ans, int start_i, int end_i, int start_j, int end_j, int cur_num, int n) { // 如果不存在遍历区间,则不会进入下面四个for循环中的任意一个 // 会持续进行递归,导致编译栈溢出 // 这种情况就是当n为某个奇数的平方且c = m = sqrt(n)的时候会出现 // 譬如填充5*5的矩阵且n = 25,最中间那个数字会出现的情况 // 额外判断这种条件,将最中间的数字填充上并退出递归即可 if (start_i == end_i && start_j == end_j) { ans[start_i][start_j] = String.valueOf(cur_num); return; } // 未填充矩阵的上边界:从左往右,固定start_i,正序遍历j for (int j = start_j; j <= end_j; j++) { ans[start_i][j] = String.valueOf(cur_num++); if (cur_num > n) return; } // 未填充矩阵的右边界:从上往下,固定end_j,正序遍历i for (int i = start_i + 1; i <= end_i; i++) { ans[i][end_j] = String.valueOf(cur_num++); if (cur_num > n) return; } // 未填充矩阵的下边界:从右往左,固定end_i,逆序遍历j for (int j = end_j - 1; j >= start_j; j--) { ans[end_i][j] = String.valueOf(cur_num++); if (cur_num > n) return; } // 未填充矩阵的左边界:从下往上,固定start_j,逆序遍历i for (int i = end_i - 1; i > start_i; i--) { ans[i][start_j] = String.valueOf(cur_num++); if (cur_num > n) return; } // 对未填充数组进行递归 // start_i, end_i, start_j, end_j需要修改 help(ans, start_i + 1, end_i - 1, start_j + 1, end_j - 1, cur_num, n); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 输入数字n,行数m int n = scanner.nextInt(); int m = scanner.nextInt(); // 计算列数c,n除以m后向上取整 int c = (int) Math.ceil((double) n / m); // 初始化答案螺旋数组,先用"*"填充 String[][] ans = new String[m][c]; for (int i = 0; i < m; i++) { for (int j = 0; j < c; j++) { ans[i][j] = "*"; } } // 递归入口:start_i, end_i, start_j, end_j // 分别传入0, m-1, 0, c-1 help(ans, 0, m - 1, 0, c - 1, 1, n); // 逐行输出螺旋矩阵 for (String[] row : ans) { System.out.println(String.join(" ", row)); } } }
#include <iostream> #include <vector> #include <cmath> using namespace std; // 递归函数 // ans为答案螺旋矩阵 // start_i, end_i, start_j, end_j分别为子矩阵的起始、终止的i和j下标 // cur_num为未填充子矩阵左上角的第一个数 // n为终止数字 void help(vector<vector<string>>& ans, int start_i, int end_i, int start_j, int end_j, int& cur_num, int n) { // 如果不存在遍历区间,则不会进入下面四个for循环中的任意一个 // 会持续进行递归,导致编译栈溢出 // 这种情况就是当n为某个奇数的平方且c = m = sqrt(n)的时候会出现 // 譬如填充5*5的矩阵且n = 25,最中间那个数字会出现的情况 // 额外判断这种条件,将最中间的数字填充上并退出递归即可 if (start_i == end_i && start_j == end_j) { ans[start_i][start_j] = to_string(cur_num); return; } // 未填充矩阵的上边界:从左往右,固定start_i,正序遍历j for (int j = start_j; j <= end_j; j++) { ans[start_i][j] = to_string(cur_num++); if (cur_num > n) return; } // 未填充矩阵的右边界:从上往下,固定end_j,正序遍历i for (int i = start_i + 1; i <= end_i; i++) { ans[i][end_j] = to_string(cur_num++); if (cur_num > n) return; } // 未填充矩阵的下边界:从右往左,固定end_i,逆序遍历j for (int j = end_j - 1; j >= start_j; j--) { ans[end_i][j] = to_string(cur_num++); if (cur_num > n) return; } // 未填充矩阵的左边界:从下往上,固定start_j,逆序遍历i for (int i = end_i - 1; i > start_i; i--) { ans[i][start_j] = to_string(cur_num++); if (cur_num > n) return; } // 对未填充数组进行递归 // start_i, end_i, start_j, end_j需要修改 help(ans, start_i + 1, end_i - 1, start_j + 1, end_j - 1, cur_num, n); } int main() { // 输入数字n,行数m int n, m; cin >> n >> m; // 计算列数c,n除以m后向上取整 int c = ceil(static_cast<double>(n) / m); // 初始化答案螺旋数组,先用"*"填充 vector<vector<string>> ans(m, vector<string>(c, "*")); int cur_num = 1; // 递归入口:start_i, end_i, start_j, end_j // 分别传入0, m-1, 0, c-1 help(ans, 0, m - 1, 0, c - 1, cur_num, n); // 逐行输出螺旋矩阵 for (const auto& row : ans) { for (const auto& elem : row) { cout << elem << " "; } cout << endl; } return 0; }
时间复杂度:O(N)
。
空间复杂度:O(MC)
。
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
绿色聊天软件戳 od1336
了解更多
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。