华为OD机试矩阵最大值:从基础遍历到流式处理的算法内功

📅 2026/7/28 10:22:12 👁️ 阅读次数 📝 编程学习
华为OD机试矩阵最大值:从基础遍历到流式处理的算法内功

1. 项目概述:从一道题看华为OD机试的“内功”考察

最近在帮几个准备冲刺华为OD(Outstanding Developer)机试的朋友做模拟训练,发现大家普遍对“矩阵最大值”这类题目感到棘手。这题在2025B卷中编号254,乍一看就是个二维数组遍历找最大值的简单问题,但实际在华为OD的考场上,它远不止于此。我翻看了近几年的真题回忆录,结合自己当年面试和后来辅导的经验,发现这道题真正的考点藏在“矩阵”这个载体和“最大值”这个目标背后,它考察的是候选人对数据结构的敏感度、边界条件的处理能力,以及在不同约束下(比如内存限制、时间复杂度要求)的算法优化思维。简单说,它考的是你写代码的“内功”,而不是花架子。

这道题通常会给你一个M x N的整数矩阵,要求找出其中的最大值。听起来是不是小学水平?但华为OD的机试从来不会出这么直白的题。题目往往会附加各种条件,比如:

  1. 矩阵可能非常大,无法一次性读入内存,需要你设计流式读取方案。
  2. 需要你同时返回最大值及其所有出现的位置(行索引和列索引),而不仅仅是值。
  3. 矩阵中可能存在多个相同的最大值,你的程序需要能正确处理。
  4. 输入格式可能不规范,需要你具备健壮的输入解析能力。
  5. 对时间和空间复杂度有明确要求,逼迫你放弃最直观的双重循环暴力解法,去思考更优的方案。

所以,当你看到“矩阵最大值”这个标题时,脑子里应该立刻拉响警报:这绝不是一个max(max(row) for row in matrix)就能搞定的事情。它是一道典型的“题干简单,坑点深”的面试题,非常适合用来区分“背题选手”和“有扎实功底的开发者”。接下来,我将以这道题为引子,拆解华为OD机试的常见套路、核心考点,并给出C++、Java、Python、C语言和JavaScript五种语言的实现参考与深度分析。无论你主攻哪门语言,都能从中找到应对这类问题的通用心法和具体招式。

2. 核心思路拆解:不止于遍历的四种境界

面对一个矩阵找最大值的问题,我们的思考不能停留在“遍历”二字上。根据题目可能隐含的不同约束,解决方案可以分为几个层次,我称之为“四种境界”。

2.1 境界一:基础遍历与信息记录

这是最直观的方法,适用于矩阵可以完全放入内存,且只需找到任意一个最大值位置的情况。思路是初始化一个变量maxVal为理论最小值(如INT_MIN),然后遍历每个元素。如果当前元素大于maxVal,则更新maxVal,并记录当前位置。这种方法的时间复杂度是O(M*N),空间复杂度是O(1)(不计输入存储)。

但是,这里就有第一个坑:如果矩阵所有元素都是理论最小值呢?比如全为INT_MIN。你的maxVal初始化成INT_MIN,那么循环结束后,maxVal依然是INT_MIN,你记录的位置可能是未初始化的垃圾值,或者是遍历的最后一个位置(如果没进if判断)。更稳健的做法是,将maxVal初始化为矩阵的第一个元素matrix[0][0],记录位置为(0,0),然后从第二个元素开始遍历。这样即使全矩阵都是同一个值,也能正确返回第一个出现的位置。

2.2 境界二:处理多个最大值位置

题目很可能要求找出“所有”最大值的位置。这时,我们需要一个列表(如vector<pair<int,int>>List<int[]>)来存储位置。在遍历时:

  • 如果当前元素> maxVal,说明我们找到了新的更大的值。此时,必须清空之前存储的所有位置列表,将maxVal更新为当前值,然后将当前位置加入列表。
  • 如果当前元素== maxVal,则直接将当前位置加入列表。
  • 如果当前元素< maxVal,则忽略。

这个逻辑清晰地区分了“更新最大值”和“记录同等最大值”两种操作,是处理此类问题的标准范式。

2.3 境界三:大矩阵与流式处理

这是华为OD机试的高频考点,也是本题的难点所在。当矩阵规模(M*N)极大,比如达到10^9数量级,无法一次性读入内存时,我们必须采用“流式处理”或“分块处理”的策略。

核心思想:我们不需要同时持有所有数据。我们可以一次读一行(或一个缓冲块),在处理完这一行后,除了需要保留的全局信息(当前已知的最大值maxVal及其位置列表),可以丢弃该行数据。

操作步骤

  1. 首先读取矩阵的行数M和列数N
  2. 初始化maxValINT_MIN(或第一个元素的值,如果流式读取能方便拿到的话),初始化一个空的位置列表posList
  3. 循环M次,每次读取一行数据。对于该行的每个元素val
    • 如果val > maxVal:更新maxVal = val,清空posList,将当前位置(currentRow, col)加入posList
    • 如果val == maxVal:将当前位置(currentRow, col)加入posList
    • 如果val < maxVal:跳过。
  4. 处理完一行后,currentRow加1,继续处理下一行。当前行数据可以被覆盖或丢弃。

这种方法的空间复杂度主要取决于存储位置列表的开销,在最坏情况下(整个矩阵都是同一个最大值),需要存储M*N个位置,这依然可能很大。但通常题目会保证这种情况不会发生,或者对输出位置的数量有隐含限制。如果连位置列表都存不下,可能需要只输出数量,或者最大值所在的区域(如行范围),这需要根据具体题目要求进行变通。

2.4 境界四:并行化与分布式思想(拓展)

对于真正海量的数据,面试官可能会考察你是否具备并行计算的思维。你可以提出,如果条件允许,可以将矩阵分块,分配给多个线程或进程同时查找每个分块内的最大值和位置,然后再进行一次全局归约(Reduce)操作,合并所有分块的结果,得到全局的最大值及其位置。这体现了你的系统设计能力和对现代计算范式的理解。虽然在机试的编码环节通常不要求实现,但在思路阐述环节提及会是加分项。

3. 多语言代码实现与深度分析

下面,我将分别用C++、Java、Python、C语言和JavaScript实现“境界二”的算法(即找出所有最大值位置),并附上详细注释和语言特性的分析。我们假设输入格式为:第一行两个整数M N,代表矩阵行数和列数,随后M行,每行N个整数。

3.1 C++实现:效率与控制的艺术

#include <iostream> #include <vector> #include <climits> // 用于INT_MIN #include <sstream> // 用于字符串解析(如果输入行不规则) using namespace std; int main() { int M, N; cin >> M >> N; // 使用vector存储位置,元素类型为pair<int, int> vector<pair<int, int>> maxPositions; int maxVal = INT_MIN; // 初始化为最小整数 // 预留空间,避免频繁扩容(小优化) maxPositions.reserve(M * N); for (int i = 0; i < M; ++i) { for (int j = 0; j < N; ++j) { int currentVal; cin >> currentVal; // 核心比较逻辑 if (currentVal > maxVal) { // 发现更大的值,清空旧列表,更新最大值 maxVal = currentVal; maxPositions.clear(); // vector的clear()是O(1)吗?不,是O(n)?这里n是size,但因为我们后续要重新填充,可以接受。也可以直接赋值为新的空vector。 // maxPositions = vector<pair<int,int>>(); // 另一种写法 maxPositions.emplace_back(i, j); // 使用emplace_back原地构造,效率优于push_back(make_pair(...)) } else if (currentVal == maxVal) { // 遇到相等的值,加入列表 maxPositions.emplace_back(i, j); } // 小于的情况,什么都不做 } } // 输出结果 cout << "最大值: " << maxVal << endl; cout << "出现位置总数: " << maxPositions.size() << endl; cout << "位置 (行, 列):" << endl; for (const auto& pos : maxPositions) { cout << "(" << pos.first << ", " << pos.second << ")" << endl; } return 0; }

C++实现要点分析:

  1. 容器选择:使用std::vector<std::pair<int,int>>来存储位置,兼顾了动态扩容和缓存友好性。pair在这里比两个独立的vector更直观。
  2. 输入处理:直接使用cin进行格式化输入,简单高效。但如果输入数据中有非预期的字符或格式错误,程序会进入错误状态。在严谨的竞赛或面试中,可能需要更健壮的输入校验(例如使用getlinestringstream)。
  3. emplace_backvspush_back:在添加位置时,使用emplace_back(i, j)直接在容器尾部构造pair对象,避免了先创建临时对象再拷贝或移动的开销,是C++11后的最佳实践。
  4. clear()的效率:在发现新的最大值时,我们调用了maxPositions.clear()clear()会销毁所有元素并将size()置为0,但capacity()通常不变(即已分配的内存不会释放)。在这个场景下,因为我们紧接着要添加新元素,保留容量可以避免立刻重新分配内存,是合理的。如果追求极致,并且确定旧列表很大而新列表可能很小,可以考虑shrink_to_fit()后再clear,但通常不需要。
  5. reserve的预分配:我们一开始调用了maxPositions.reserve(M*N)。这是一个优化,避免了vector在增长过程中多次重新分配内存和拷贝数据。虽然最坏情况下(矩阵所有元素都是最大值)确实会用上这么多空间,但这也是一种空间换时间的策略。如果担心内存,可以不预留,让vector自然增长。

3.2 Java实现:健壮与面向对象

import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class MatrixMaxValue { // 用一个内部类来封装位置信息,比用int[]更清晰 static class Position { int row; int col; Position(int row, int col) { this.row = row; this.col = col; } @Override public String toString() { return "(" + row + ", " + col + ")"; } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int M = scanner.nextInt(); int N = scanner.nextInt(); List<Position> maxPositions = new ArrayList<>(); // 注意:Java中int的最小值用Integer.MIN_VALUE int maxVal = Integer.MIN_VALUE; // 同样可以预估容量,但ArrayList的ensureCapacity不是公共方法。通常构造函数传入初始容量。 // List<Position> maxPositions = new ArrayList<>(M * N); // 可行,但可能过度分配 for (int i = 0; i < M; i++) { for (int j = 0; j < N; j++) { int currentVal = scanner.nextInt(); if (currentVal > maxVal) { maxVal = currentVal; maxPositions.clear(); // 清空列表 maxPositions.add(new Position(i, j)); } else if (currentVal == maxVal) { maxPositions.add(new Position(i, j)); } } } scanner.close(); // 好习惯,关闭资源 System.out.println("最大值: " + maxVal); System.out.println("出现位置总数: " + maxPositions.size()); System.out.println("位置 (行, 列):"); for (Position pos : maxPositions) { System.out.println(pos); // 这里会调用Position的toString方法 } } }

Java实现要点分析:

  1. 内部类Position:没有内置的Pair类型(直到Java 14的Record特性,但面试环境可能不支持),定义一个简单的内部类来封装行列信息,比使用int[2]List<Integer>更具可读性和类型安全。重写toString()方法便于输出。
  2. ArrayList的使用ArrayList是动态数组,与C++的vector类似。注意它的clear()方法会将所有引用置为null以便垃圾回收,但底层数组容量不变。
  3. 初始容量:可以通过new ArrayList<>(initialCapacity)来指定初始容量,避免扩容。这里M*N是最坏情况,可能分配过多。一个折中的办法是预估一个较小的值,或者不指定。
  4. 资源管理:使用try-with-resources(Java 7+)是更优雅的方式来自动关闭Scanner。这里显式调用scanner.close()也是好习惯。
  5. Integer.MIN_VALUE:注意是Integer.MIN_VALUE,不是INT_MIN

3.3 Python实现:简洁与高效

import sys def find_max_positions(): # 读取第一行,获取M和N data = sys.stdin.read().strip().split() if not data: return # 将字符串列表转换为整数迭代器,方便逐个取出 it = iter(data) M = int(next(it)) N = int(next(it)) max_val = float('-inf') # Python中表示负无穷大,用于初始化 max_positions = [] for i in range(M): for j in range(N): # 从迭代器中取出下一个值并转换为整数 current_val = int(next(it)) if current_val > max_val: max_val = current_val max_positions.clear() # 清空列表 max_positions.append((i, j)) elif current_val == max_val: max_positions.append((i, j)) # 小于的情况忽略 # 输出结果 print(f"最大值: {max_val}") print(f"出现位置总数: {len(max_positions)}") print("位置 (行, 列):") for pos in max_positions: print(pos) if __name__ == "__main__": find_max_positions()

Python实现要点分析:

  1. 输入处理:一次性读取所有输入(sys.stdin.read())然后分割,在处理大数据量时可能占用较多内存,但代码简洁。对于流式大矩阵,应改为逐行读取(sys.stdin.readline())。这里为了代码清晰,使用了前者。使用iter()next()可以避免在循环中反复进行列表索引操作,稍微提升效率。
  2. 初始化最大值:使用float('-inf')表示负无穷,这是一个安全的初始值,保证任何整数都比它大。也可以读取第一个元素来初始化。
  3. 元组存储位置:使用(i, j)这样的元组(tuple)来存储位置,非常轻量且自然。listclear()方法效率很高。
  4. 格式化输出:使用f-string(Python 3.6+)进行字符串格式化,清晰易读。
  5. 性能考虑:Python的循环在数值计算上较慢。如果矩阵非常大且性能是关键,可以考虑使用NumPy库(如果环境允许),其底层是C实现,速度极快。但机试环境通常不允许安装第三方库,所以掌握纯Python的优化技巧(如避免不必要的函数调用、使用局部变量等)更重要。

3.4 C语言实现:底层与精准控制

#include <stdio.h> #include <stdlib.h> #include <limits.h> // 包含INT_MIN的定义 // 定义位置结构体 typedef struct { int row; int col; } Position; int main() { int M, N; if (scanf("%d %d", &M, &N) != 2) { fprintf(stderr, "输入格式错误\n"); return 1; } // 动态分配位置数组。最坏情况需要M*N个位置。 Position *maxPositions = (Position *)malloc(M * N * sizeof(Position)); if (maxPositions == NULL) { fprintf(stderr, "内存分配失败\n"); return 1; } int maxPositionsSize = 0; // 当前有效位置数量 int maxVal = INT_MIN; for (int i = 0; i < M; i++) { for (int j = 0; j < N; j++) { int currentVal; if (scanf("%d", &currentVal) != 1) { fprintf(stderr, "读取矩阵元素错误\n"); free(maxPositions); return 1; } if (currentVal > maxVal) { maxVal = currentVal; maxPositionsSize = 0; // “清空”数组,只需重置大小 maxPositions[maxPositionsSize].row = i; maxPositions[maxPositionsSize].col = j; maxPositionsSize++; } else if (currentVal == maxVal) { // 需要检查是否还有空间(理论上应该还有,因为我们分配了M*N) // 但在严谨的程序中,可以加入检查 if (maxPositionsSize < M * N) maxPositions[maxPositionsSize].row = i; maxPositions[maxPositionsSize].col = j; maxPositionsSize++; } } } printf("最大值: %d\n", maxVal); printf("出现位置总数: %d\n", maxPositionsSize); printf("位置 (行, 列):\n"); for (int k = 0; k < maxPositionsSize; k++) { printf("(%d, %d)\n", maxPositions[k].row, maxPositions[k].col); } // 释放动态分配的内存 free(maxPositions); return 0; }

C语言实现要点分析:

  1. 手动内存管理:这是C语言的核心特点,也是难点。我们为位置数组maxPositions动态分配了最大可能需要的空间(M * N * sizeof(Position))。必须检查malloc的返回值是否为NULL,并在程序结束前用free释放内存,否则会造成内存泄漏。
  2. “清空”操作:由于使用数组和size指针,我们无法像高级语言那样“清空”容器。发现新的最大值时,我们只需将maxPositionsSize重置为0,后续的写入自然会覆盖旧数据。这是一种高效的做法。
  3. 错误处理:C语言没有异常机制,必须对每一步可能出错的I/O操作(scanf)进行检查,并返回适当的错误码。这增加了代码的健壮性,也使其看起来更复杂。
  4. 结构体使用:使用typedef定义Position结构体,比用两个独立的数组管理行和列更清晰。
  5. 效率:这种实现方式在空间上可能过度分配(最坏情况才用满),但避免了动态扩容的开销,时间效率很高。如果内存非常紧张,可以考虑使用realloc进行动态扩容,但会引入更复杂的逻辑和潜在的性能抖动。

3.5 JavaScript (Node.js)实现:异步与事件驱动思维

const readline = require('readline'); function findMatrixMax() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let M, N; let currentRow = 0; let currentCol = 0; let maxVal = -Infinity; let maxPositions = []; let isFirstLine = true; rl.on('line', (line) => { const tokens = line.trim().split(/\s+/).map(Number); // 按空白字符分割并转数字 if (isFirstLine) { // 第一行是M和N [M, N] = tokens; isFirstLine = false; // 可以在这里初始化maxPositions的预估长度(但JS数组是动态的) // maxPositions = new Array(M * N); // 预分配大小,但意义不大 } else { // 处理矩阵行数据 for (let val of tokens) { if (currentCol >= N) { // 理论上应该刚好,这里做安全保护 console.error(`第${currentRow+1}行数据超过${N}列`); rl.close(); return; } if (val > maxVal) { maxVal = val; maxPositions.length = 0; // 清空数组的快速方法 maxPositions.push([currentRow, currentCol]); } else if (val === maxVal) { maxPositions.push([currentRow, currentCol]); } currentCol++; } // 一行处理完毕,列索引归零,行索引加一 if (currentCol !== N) { console.error(`第${currentRow+1}行数据不足${N}列`); rl.close(); return; } currentCol = 0; currentRow++; // 如果所有行都处理完毕 if (currentRow === M) { rl.close(); // 关闭输入流,触发'close'事件 } } }); rl.on('close', () => { // 所有行处理完毕,输出结果 console.log(`最大值: ${maxVal}`); console.log(`出现位置总数: ${maxPositions.length}`); console.log("位置 (行, 列):"); for (let pos of maxPositions) { console.log(`(${pos[0]}, ${pos[1]})`); } // 如果提前因为错误关闭,这里可能还没处理完所有数据,但错误信息已输出。 }); } // 执行函数 findMatrixMax();

JavaScript实现要点分析:

  1. 异步流式读取:使用readline模块逐行读取输入,非常适合处理可能很大的输入流,内存占用小。这是Node.js处理标准输入的标准方式。
  2. 事件驱动:通过监听'line'事件处理每一行,监听'close'事件在输入结束后输出结果。代码逻辑是异步的,需要管理好状态(currentRow,currentCol,maxVal等)。
  3. 数组清空maxPositions.length = 0是清空一个数组最高效的方法之一,比maxPositions = []更好,因为后者会创建一个新数组,可能导致旧的数组内存不被立即回收(取决于JS引擎)。
  4. 错误处理:增加了对每行列数是否匹配的检查,增强了程序的健壮性。
  5. -Infinity:使用-Infinity来初始化最大值,类似于Python的float('-inf'),确保任何数字都比它大。
  6. 使用数组存储位置:用[row, col]这样的数组表示位置,简单方便。也可以使用对象{row, col},但数组更简洁。

4. 常见“坑点”与实战调试技巧

即使理解了算法,在实现时还是会踩到各种各样的坑。下面我总结几个在实现“矩阵最大值”及其变体时最容易出错的地方,以及调试方法。

4.1 输入格式的陷阱

问题:题目说“随后M行,每行N个整数”,但没说是用空格隔开还是用逗号?行末可能有多余的空格吗?矩阵数据之后会不会有额外的空行或字符?

应对策略

  • C++/Java:使用nextInt()cin >> int通常能自动跳过空白字符(空格、制表符、换行),比较健壮。但如果可能出现非数字字符,需要用hasNextInt()判断或捕获异常。
  • Pythonsys.stdin.read().split()会将所有空白字符作为分隔符,简单暴力。逐行读取时,用line.strip().split()
  • C语言scanf("%d", ...)也会跳过空白字符,但遇到非数字输入会出错且难以恢复。对于复杂格式,建议用fgets读整行再用sscanfstrtok解析。
  • JavaScriptline.trim().split(/\s+/)可以处理任意数量的空白字符。

调试技巧:在本地测试时,故意构造“脏数据”进行测试,比如行尾多空格、空行、非数字字符等,看你的程序是否会崩溃或得到错误结果。

4.2 初始化最大值的陷阱

问题:将maxVal初始化为0。如果矩阵中所有值都是负数,那么结果最大值就会错误地是0,而实际上应该是那个最大的负数。

正确做法:初始化为理论最小值。对于整数,各语言常用:

  • C++/C:INT_MIN(#include <climits><limits.h>)
  • Java:Integer.MIN_VALUE
  • Python:float('-inf')或读取第一个元素matrix[0][0]
  • JavaScript:-Infinity

4.3 比较浮点数(如果矩阵元素是浮点数)

问题:如果矩阵元素是doublefloat,直接使用==比较是否相等是不可靠的,因为浮点数有精度误差。

正确做法:比较两个浮点数ab是否“相等”,应该判断它们的差的绝对值是否小于一个很小的数(epsilon)。

def is_equal(a, b, epsilon=1e-9): return abs(a - b) < epsilon

在最大值判断逻辑中,需要将currentVal == maxVal替换为is_equal(currentVal, maxVal)。同时,在currentVal > maxVal的判断中,也要考虑“等于”的情况,通常的逻辑是:如果currentVal > maxVal + epsilon,则认为currentVal更大。

4.4 位置索引的起始值

问题:题目要求输出的行号和列号是从0开始还是从1开始?这看似简单,却极易出错。机试题目通常会在描述中说明,但有时描述模糊。

应对策略

  1. 仔细审题:题目描述中寻找“下标从0开始”或“索引从1开始”的字眼。
  2. 看样例:样例输入和输出是最好的说明。如果样例输出是(1, 1)对应第一个元素,那么就是从1开始。
  3. 代码适应性:在存储位置时,可以先按从0开始存储。在最终输出前,根据题目要求决定是否对每个位置的行列索引+1。这样只需修改一处。

4.5 流式处理中的状态维护

问题:在流式读取(一次读一行)时,你需要自己维护当前的行索引i和列索引j。容易出错的地方是:

  • 在每行开始时,忘记将列索引j重置为0。
  • 在处理完一行后,行索引i的递增时机不对。
  • 如果一行的数据通过多次读取才凑齐(比如网络分包),状态维护会更复杂。

调试技巧:在代码中添加详细的日志,打印出每次读取数据时的ijcurrentVal,以及maxValmaxPositions的变化情况。对于小矩阵,可以人工验证。

4.6 内存与性能优化

问题:当矩阵极大,且最大值非常多时,存储所有位置的列表可能耗尽内存。

优化思路

  1. 只存一个:如果题目只要求最大值,不要求位置,那根本不用存位置列表。
  2. 只存第一个或最后一个:如果题目只要求任意一个最大值位置。
  3. 压缩存储:如果矩阵非常稀疏,最大值只出现在少数行,可以只记录行号,然后在该行内记录所有列号。
  4. 分治归约:如果真的需要所有位置且内存不足,可能需要使用外部排序或MapReduce思想,将矩阵分块,先找每个块的最大值和位置,再合并。这通常已超出机试范围,但可以作为思路讨论。

5. 从本题延伸的华为OD机试备战策略

通过深度剖析“矩阵最大值”这一道题,我们可以提炼出应对华为OD机试的通用策略:

  1. 题目再简单,也要多想一步:永远不要相信表面题意。看到“找最大值”,立刻想到“多个最大值怎么办?”“最大值有多个怎么存?”“矩阵太大怎么办?”“输入格式可能有什么坑?”。养成这种条件反射式的深度思考习惯。

  2. 掌握基础数据结构的多种语言实现:尤其是数组、字符串、链表、哈希表(字典/映射)、栈、队列。清楚它们在C++(STL)、Java(Collections)、Python(list, dict, set)、C(需要自己实现或使用简单数组)和JavaScript(Array, Object, Map/Set)中的API、性能特性和常见坑点。

  3. 熟练处理输入输出:这是机试的“第一道关”。务必掌握你所选语言中,高效、健壮地读取各种格式(空格分隔、逗号分隔、不定长)输入的方法。输出格式也要严格符合题目要求,多一个少一个空格都可能导致错误。

  4. 复杂度分析是必备技能:写完代码,要能立刻说出时间复杂度和空间复杂度。对于任何操作,都要问自己“有没有更优的方法?”。例如,本题找最大值,时间复杂度O(MN)已经是最优(必须遍历每个元素至少一次),但空间复杂度可以从O(MN)(存所有位置)优化到O(1)(如果只存一个值)或O(K)(K为最大值个数)。

  5. 测试用例设计能力:给自己出测试用例。包括:

    • 常规用例:普通矩阵,有正有负。
    • 边界用例:矩阵只有1x1,只有一行,只有一列。
    • 特殊值用例:所有元素相同,所有元素都是负数,所有元素都是正数,最大值有多个且分散。
    • 极限用例:矩阵非常大(思考你的程序是否会内存溢出或超时)。
    • 错误格式用例:输入中包含非数字字符(如果你的程序需要处理)。
  6. 代码风格与可读性:即使是在紧张的机试中,也要尽量写出结构清晰、命名合理、有适当注释的代码。这不仅能减少你自己的错误,也能给阅卷人(或系统)留下好印象。使用有意义的变量名(如maxVal而不是mv),将复杂逻辑封装成函数。

这道“矩阵最大值”题,就像一面镜子,照出了程序员的基本功。它不追求高深的算法,但把基础的数据处理、流程控制、边界情况、语言特性都考了个遍。把这些细节都处理好,你在华为OD机试中面对任何题目,都能多一份从容和把握。