疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。
他发明了一种写法:给出数字个数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
#include
#include
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
了解更多