可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳od1441
了解算法冲刺训练(备注【CSDN】否则不通过)
文章目录
相关推荐阅读
- 【华为OD机考正在更新】2025年双机位A卷真题【完全原创题解 | 详细考点分类 | 不断更新题目 | 六种主流语言Py+Java+Cpp+C+Js+Go】
- 【华为OD机考】2025C+2025B+2024E+D卷真题【完全原创题解 | 详细考点分类 | 不断更新题目】
- 【华为OD笔试】双机位A+2025C+2025B+2024E+D卷真题机考套题汇总【真实反馈,不断更新,限时免费】
- 【华为OD笔试】2024E+D卷命题规律解读【分析500+场OD笔试考点总结】
- 【华为OD流程】性格测试选项+注意事项】
题目练习网址:【模拟】双机位A-螺旋数字矩阵
题目描述与示例
题目描述
疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。
他发明了一种写法:给出数字个数n
和行数m
(0 < n < 999,0 < m < 999)
,从左上角的1
开始,按照顺时针螺旋向内写方式,依次写出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
显然每一次填充,都是从未填充矩阵的左上角开始填充其外层,直到填充完毕或到达规定数字。
因此可以使用递归来完成上述过程。
首先我们需要初始化一个均由"*"
填充的m``*``c
的二维矩阵ans
。
# 计算列数c,为n除以m后向上取整
c = ceil(n / m)
# 初始化答案螺旋数组,先用"*"填充
ans = [["*"] * c for _ in range(m)]
在递归过程中,我们需要
- 四个参数
start_i
,end_i
,start_j
,end_j
分别为子矩阵的起始、终止的i
和j
下标。即
start_j end_j
↓ ↓
start_i → * * * * *
* * * * *
* * * * *
* * * * *
end_i → * * * * *
cur_num
为未填充子矩阵左上角的第一个数- 填充结束的终止数字
n
以填充子矩阵的上边界为例,我们需要固定start_i
,从左往右(从start_j
到end_j
)遍历j
。
在遍历过程中,我们需要把ans[start_i][j]
修改为str(cur_num)
,同时增加cur_num
,且一旦发现cur_num
大于n
,则可以直接退出函数。代码为
def help(ans, start_i, end_i, start_j, end_j, cur_num, n):
pass
# 未填充矩阵的上边界:从左往右,固定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
pass
同理我们可以构建出填充子矩阵其他遍历方向的代码
def help(ans, start_i, end_i, start_j, end_j, cur_num, n):
pass
# 未填充矩阵的上边界:从左往右,固定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
pass
在所有遍历和边界填充完成之后,我们需要进行递归函数的调用。
注意到在下一层递归中,四个边界分别变成了start_i+1
, end_i-1
, start_j+1
, end_j-1
。即代码为
def help(ans, start_i, end_i, start_j, end_j, cur_num, n):
pass
# 未填充矩阵的上边界:从左往右,固定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,逆序遍历i
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)
除此之外,还需要考虑一种特殊情况。
如果遍历区间不存在,则不会进入四个for
循环中的任意一个,会持续进行递归,导致编译栈溢出。
这种情况就是当n
为某个奇数的平方且c = m = sqrt(n)
的时候会出现。
譬如填充5*5
的矩阵且n = 25
,最中间那个数字会出现的情况。
我们还需要额外判断这种条件,将最中间的数字填充上并退出递归即可。修改代码为
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)
剩下的内容就是递归入口的调用了。
分别对start_i
, end_i
, start_j
, end_j
传入0
, m-1
, 0
, c-1
即可。
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, 1, n)
代码
Python
# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com
# 华为OD机试刷题网站:https://www.algomooc.com
# 添加微信 278166530 获取华为 OD 笔试真题题库和视频
# 题目:【模拟】2024D/2024E/2025A/2025B/双机位A-螺旋矩阵
# 分值: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)
Java
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));
}
}
}
C++
#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;
}
C
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// 递归函数
// ans为答案螺旋矩阵
// start_i, end_i, start_j, end_j分别为子矩阵的起始、终止的i和j下标
// cur_num为未填充子矩阵左上角的第一个数
// n为终止数字
void fillSpiral(int **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] = *cur_num;
return;
}
// 未填充矩阵的上边界:从左往右,固定start_i,正序遍历j
for (int j = start_j; j < end_j; j++) {
ans[start_i][j] = (*cur_num)++;
if (*cur_num > n) return;
}
// 未填充矩阵的右边界:从上往下,固定end_j,正序遍历i
for (int i = start_i; i < end_i; i++) {
ans[i][end_j] = (*cur_num)++;
if (*cur_num > n) return;
}
// 未填充矩阵的下边界:从右往左,固定end_i,逆序遍历j
for (int j = end_j; j > start_j; j--) {
ans[end_i][j] = (*cur_num)++;
if (*cur_num > n) return;
}
// 未填充矩阵的左边界:从下往上,固定start_j,逆序遍历i
for (int i = end_i; i > start_i; i--) {
ans[i][start_j] = (*cur_num)++;
if (*cur_num > n) return;
}
// 对未填充数组进行递归
// start_i, end_i, start_j, end_j需要修改
fillSpiral(ans, start_i + 1, end_i - 1, start_j + 1, end_j - 1, cur_num, n);
}
int main() {
int n, m;
// 输入数字n,行数m
scanf("%d %d", &n, &m);
// 计算列数c,n除以m后向上取整
int c = (int)ceil((double)n / m);
// 初始化答案螺旋数组,先用0填充
int **ans = (int **)malloc(m * sizeof(int *));
for (int i = 0; i < m; i++) {
ans[i] = (int *)malloc(c * sizeof(int));
for (int j = 0; j < c; j++) {
ans[i][j] = 0;
}
}
int cur_num = 1;
// 递归入口:start_i, end_i, start_j, end_j
// 分别传入0, m-1, 0, c-1
fillSpiral(ans, 0, m - 1, 0, c - 1, &cur_num, n);
// 逐行输出螺旋矩阵
for (int i = 0; i < m; i++) {
for (int j = 0; j < c; j++) {
if (ans[i][j] == 0) {
printf("*");
} else {
printf("%d", ans[i][j]);
}
if (j < c - 1) {
printf(" ");
}
}
printf("\n");
}
// 释放内存
for (int i = 0; i < m; i++) {
free(ans[i]);
}
free(ans);
return 0;
}
Node JavaScript
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('', (input) => {
const [n, m] = input.split(' ').map(Number);
const c = Math.ceil(n / m);
// 初始化答案螺旋数组,先用""填充
* const ans = Array.from({ length: m }, () => Array(c).fill("*"));
// 递归函数
function help(start_i, end_i, start_j, end_j, cur_num) {
if (start_i === end_i && start_j === end_j) {
ans[start_i][start_j] = cur_num.toString();
return;
}
// 上边界
for (let j = start_j; j < end_j; j++) {
ans[start_i][j] = cur_num.toString();
cur_num++;
if (cur_num > n) return;
}
// 右边界
for (let i = start_i; i < end_i; i++) {
ans[i][end_j] = cur_num.toString();
cur_num++;
if (cur_num > n) return;
}
// 下边界
for (let j = end_j; j > start_j; j--) {
ans[end_i][j] = cur_num.toString();
cur_num++;
if (cur_num > n) return;
}
// 左边界
for (let i = end_i; i > start_i; i--) {
ans[i][start_j] = cur_num.toString();
cur_num++;
if (cur_num > n) return;
}
// 递归处理内部子矩阵
help(start_i + 1, end_i - 1, start_j + 1, end_j - 1, cur_num);
}
// 初始化递归
help(0, m - 1, 0, c - 1, 1);
// 输出结果
ans.forEach(row => console.log(row.join(' ')));
rl.close();
});
Go
package main
import (
"fmt"
"math"
"strconv"
)
// 递归函数
// ans为答案螺旋矩阵
// start_i, end_i, start_j, end_j分别为子矩阵的起始、终止的i和j下标
// curNum为未填充子矩阵左上角的第一个数
// n为终止数字
func fillSpiral(ans [][]string, startI, endI, startJ, endJ, curNum, n int) {
// 如果不存在遍历区间,则不会进入下面四个for循环中的任意一个
// 会持续进行递归,导致编译栈溢出
// 这种情况就是当n为某个奇数的平方且c = m = sqrt(n)的时候会出现
// 譬如填充5*5的矩阵且n = 25,最中间那个数字会出现的情况
// 额外判断这种条件,将最中间的数字填充上并退出递归即可
if startI == endI && startJ == endJ {
ans[startI][startJ] = strconv.Itoa(curNum)
return
}
// 未填充矩阵的上边界:从左往右,固定startI,正序遍历j
for j := startJ; j < endJ; j++ {
ans[startI][j] = strconv.Itoa(curNum)
curNum++
if curNum > n {
return
}
}
// 未填充矩阵的右边界:从上往下,固定endJ,正序遍历i
for i := startI; i < endI; i++ {
ans[i][endJ] = strconv.Itoa(curNum)
curNum++
if curNum > n {
return
}
}
// 未填充矩阵的下边界:从右往左,固定endI,逆序遍历j
for j := endJ; j > startJ; j-- {
ans[endI][j] = strconv.Itoa(curNum)
curNum++
if curNum > n {
return
}
}
// 未填充矩阵的左边界:从下往上,固定startJ,逆序遍历i
for i := endI; i > startI; i-- {
ans[i][startJ] = strconv.Itoa(curNum)
curNum++
if curNum > n {
return
}
}
// 对未填充数组进行递归
// startI, endI, startJ, endJ需要修改
fillSpiral(ans, startI+1, endI-1, startJ+1, endJ-1, curNum, n)
}
func main() {
// 输入数字n,行数m
var n, m int
fmt.Scan(&n, &m)
// 计算列数c,n除以m后向上取整
c := int(math.Ceil(float64(n) / float64(m)))
// 初始化答案螺旋数组,先用"*"填充
ans := make([][]string, m)
for i := range ans {
ans[i] = make([]string, c)
for j := range ans[i] {
ans[i][j] = "*"
}
}
// 递归入口:startI, endI, startJ, endJ
// 分别传入0, m-1, 0, c-1
curNum := 1
fillSpiral(ans, 0, m-1, 0, c-1, curNum, n)
// 逐行输出螺旋矩阵
for _, row := range ans {
for j, elem := range row {
if j > 0 {
fmt.Print(" ")
}
fmt.Print(elem)
}
fmt.Println()
}
}
时空复杂度
时间复杂度:O(N)
。需要填充N
个数字。
空间复杂度:O(MC)
。忽略递归所需要的编译栈空间,需要构建大小为c*m
的二维矩阵ans
。
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华子OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务1000+同学成功上岸!
-
课程讲师为全网200w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
90+天陪伴式学习,100+直播课时,300+动画图解视频,500+LeetCode经典题,500+华为OD真题/大厂真题,还有简历修改、模拟面试、陪伴小群、资深HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接OD真题汇总(持续更新)
-
绿色聊天软件戳
od1441
或了解更多