华为OD机试高频题:计算单词平均重量的多语言实现与避坑指南 1. 项目概述从一道机试题看编程基本功最近在帮几个准备参加华为OD机试的朋友做模拟练习发现“计算句子中的单词平均重量”这道题出现的频率相当高。乍一看这题目简单得有点“小儿科”——不就是数数单词长度然后算个平均数吗但实际在限时、高压的机试环境中能把这道题做得又快又好、代码既清晰又健壮还真能拉开不小的差距。它就像一面镜子能清晰地照出一个程序员的基础是否扎实对细节的把握是否到位。无论是用C、Java、JavaScript还是Python这道题都值得拿出来细细拆解一番。这道题的核心需求非常明确给定一个英文句子由空格分隔的单词组成计算所有单词长度的算术平均值。但别急着写split和sum题目里埋着几个典型的“坑点”比如句子前后可能有空格、单词间可能有连续空格、以及最终结果需要保留几位小数这些细节恰恰是机试考官重点考察的地方它们区分了“能运行”的代码和“高质量”的代码。接下来我就结合自己多年刷题和面试的经验把这题的里里外外、各种语言的实现套路以及避坑指南给大家一次性讲透。2. 核心需求解析与“坑点”预判在动手写任何代码之前我们必须像侦探一样把题目的需求彻底吃透并预判所有可能的边界情况。这是写出鲁棒性代码的第一步也是机试中避免无谓失分的关键。2.1 问题定义与输入输出规格根据常见的题目描述如牛客网、OD真题题库中的版本我们可以将需求明确如下输入一个字符串代表一个英文句子。例如” Hello World! This is a test. “输出一个浮点数表示句子中所有单词的平均长度重量。通常要求保留两位小数。单词定义由空格分隔的连续非空格字符序列。标点符号通常被视为单词的一部分例如”World!”的长度是6W, o, r, l, d, !。这里的关键在于对“空格”和“单词”的界定。题目中的“空格”一般指标准的空格字符ASCII 32而“单词”是去除句子首尾空格后被一个或多个空格分隔开的子串。2.2 隐藏的边界条件与常见“坑点”如果只是处理”Hello World”这样的理想句子那这题就失去了意义。机试的测试用例一定会包含各种边界情况来考验你。以下是我总结的几类典型“坑”首尾空格输入可能是” Hello”或”World “甚至” Hello World “。你的算法必须能正确处理这些无关的空格。连续空格单词之间可能有多个空格如”Hello World”。你的单词分割逻辑不能因为连续空格而产生空字符串。空句子或纯空格句子输入可能是空字符串””或” “。这种情况下没有单词平均长度该如何定义通常题目会说明“至少有一个单词”但严谨的做法是进行处理比如返回0.00并在代码中加以判断避免除零错误。标点符号如前所述标点通常算作单词长度的一部分。这一点需要明确不能擅自去除标点。输出精度这是最容易忽略的一点。题目要求保留两位小数你直接用float或double计算后输出可能会因为浮点数精度问题得到3.6666666667而不是3.67。必须使用格式化输出。注意在开始编码前务必在心里或纸上把这些用例过一遍”Hello World”、” Hello World “、”Hello World”、”a”、” “、””。想清楚你的算法对每个用例会输出什么。2.3 算法思路选择解决这个问题的算法思路非常直接属于“模拟”或“字符串处理”类别预处理去除输入字符串首尾的空格Trim。这一步可以优雅地处理首尾空格问题。分割将处理后的字符串按空格进行分割得到单词数组。关键点分割时要能处理连续空格确保得到的数组里没有空字符串。计算遍历单词数组累加每个单词的长度得到总长度totalLength。统计单词数组的元素个数得到单词总数wordCount。计算平均重量averageWeight totalLength / wordCount。格式化输出将averageWeight格式化为保留两位小数的字符串。整个算法的核心时间复杂度是O(n)n为字符串长度空间复杂度则取决于分割时是否创建了新的数组O(m)m为单词数。3. 多语言实现详解与代码对比不同的编程语言在字符串处理上提供了不同的工具包实现风格和需要注意的细节也各不相同。下面我将分别用C、Java、JavaScript和Python给出实现并对比其中的异同和注意事项。3.1 C 实现手动控制与效率C的实现相对底层需要更关注细节和内存/性能适合展示扎实的基本功。#include iostream #include string #include iomanip // 用于格式化输出 #include sstream #include vector using namespace std; double calculateAverageWordWeight(const string sentence) { // 1. 去除首尾空格 (C11 起) size_t start sentence.find_first_not_of( ); if (start string::npos) { // 字符串全是空格或为空 return 0.0; } size_t end sentence.find_last_not_of( ); string trimmed sentence.substr(start, end - start 1); // 2. 分割单词处理连续空格 vectorstring words; istringstream iss(trimmed); string word; while (iss word) { // 流提取操作符会自动跳过空白字符 words.push_back(word); } // 3. 计算总长度和单词数 if (words.empty()) { return 0.0; } int totalLength 0; for (const auto w : words) { totalLength w.length(); } // 4. 计算平均值 return static_castdouble(totalLength) / words.size(); } int main() { string input; // 使用 getline 读取整行包括空格 while (getline(cin, input)) { double avg calculateAverageWordWeight(input); // 格式化输出固定小数点后两位 cout fixed setprecision(2) avg endl; } return 0; }C实现要点解析去空格没有直接的trim函数需要借助find_first_not_of和find_last_not_of手动查找非空格位置。string::npos是一个特殊值表示未找到。分割这里使用了istringstream配合操作符。iss word会持续从流中提取字符串并自动跳过任何空白字符包括空格、制表符、换行等这完美解决了连续空格的问题且效率较高。格式化输出fixed和setprecision(2)是黄金搭档确保输出固定两位小数而不是科学计数法。注意操作符默认以空白字符分隔如果题目明确单词分隔符只有空格这是最佳选择。如果分隔符更复杂可能需要使用getline配合指定分隔符。3.2 Java 实现健壮性与API运用Java的字符串API非常丰富实现起来更简洁但要注意split方法的陷阱。import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner new Scanner(System.in); while (scanner.hasNextLine()) { String sentence scanner.nextLine(); double avg calculateAverageWordWeight(sentence); // 使用 String.format 或 System.out.printf 格式化输出 System.out.printf(%.2f%n, avg); } scanner.close(); } public static double calculateAverageWordWeight(String sentence) { // 1. 去除首尾空格 String trimmed sentence.trim(); // 2. 分割单词 - 这里是关键 // 错误做法String[] words trimmed.split( ); // 连续空格会产生空字符串 // 正确做法使用正则表达式 \s 匹配一个或多个空白字符 String[] words trimmed.split(\\s); // 3. 处理全空格或空字符串的情况 if (words.length 0 || (words.length 1 words[0].isEmpty())) { return 0.0; } // 4. 计算总长度 int totalLength 0; for (String word : words) { totalLength word.length(); } // 5. 计算平均值 return (double) totalLength / words.length; } }Java实现要点解析trim()方法非常方便直接去除首尾空白字符。split的坑这是Java实现中最容易出错的地方。split( )在遇到连续空格时会产生空字符串数组元素。例如”a b”会分割成[“a”, “”, “”, “b”]。必须使用split(“\\s”)其中\\s是正则表达式表示一个或多个空白字符。空数组判断即使使用了\\s对于纯空格字符串” “trim()后变成””split(“\\s”)会得到一个包含一个空字符串的数组[“”]。所以需要额外判断。格式化输出System.out.printf(“%.2f%n”, avg)是最简洁的方式%n是平台无关的换行符。3.3 JavaScript 实现灵活与异步考量JavaScript在牛客网等OJ平台通常以Node.js环境运行需要注意其异步读取输入的方式。const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); function calculateAverageWordWeight(sentence) { // 1. 去除首尾空格 const trimmed sentence.trim(); // 2. 分割单词使用正则匹配连续空格 // split 参数如果是字符串则不会合并连续分隔符。必须用正则。 const words trimmed.split(/\s/); // 3. 处理空字符串情况trim后为空 if (words.length 0 || (words.length 1 words[0] )) { return 0.0; } // 4. 计算总长度 const totalLength words.reduce((sum, word) sum word.length, 0); // 5. 计算平均值 return totalLength / words.length; } rl.on(line, (input) { const avg calculateAverageWordWeight(input); // 使用 toFixed 进行四舍五入并格式化为两位小数字符串 // 注意toFixed 返回的是字符串且会进行四舍五入。 console.log(avg.toFixed(2)); }); // 如果题目是多行输入可能需要监听 close 事件 // rl.on(close, () process.exit(0));JavaScript实现要点解析trim()与Java类似很方便。split同样split(‘ ’)会出问题。必须使用正则表达式split(/\s/)。数组求和使用reduce方法可以优雅地计算总长度这是函数式编程的常见做法。格式化输出Number.toFixed(2)方法直接搞定返回的是字符串。注意它执行的是“四舍六入五成双”的银行家舍入法但在大多数简单场景下与四舍五入无异。输入处理Node.js环境下需要使用readline模块逐行读取输入。这是机试时必须掌握的套路。3.4 Python 实现极致简洁与可读性Python以其强大的内置函数和清晰的语法让这道题的实现变得异常简洁。import sys def calculate_average_word_weight(sentence: str) - float: # 1. 去除首尾空格 trimmed sentence.strip() # 2. 分割单词split() 不带参数时默认按任意空白字符分割并自动忽略连续空白和首尾空白。 # 这是最简洁且正确的方式 words trimmed.split() # 3. 处理全空格情况split() 对于空字符串或全空格字符串返回空列表 if not words: return 0.0 # 4. 计算总长度和平均值 # 使用列表推导式和 sum 函数一行代码完成 total_length sum(len(word) for word in words) return total_length / len(words) if __name__ __main__: for line in sys.stdin: line line.rstrip(\n) # 去除行尾换行符但保留句中空格 avg calculate_average_word_weight(line) # 使用 format 或 f-string 格式化输出 print(f{avg:.2f})Python实现要点解析str.strip()去除首尾空白字符。str.split()的魔法这是Python实现比其它语言简洁的核心。当split()不传入任何参数时它会按任意空白字符空格、制表符、换行等进行分割并且自动过滤掉所有的空字符串无论中间有多少连续空白。这完美契合了本题的所有要求。空列表判断对于空输入””或全空格” “split()返回空列表[]直接判断即可。列表推导式与sumsum(len(word) for word in words)是Pythonic的求和方法高效且可读。格式化输出f-stringf”{avg:.2f}”是当前最推荐的格式化方式清晰直观。4. 机试实战技巧与避坑指南掌握了代码实现只是第一步在真实的机试环境中如何快速、准确、高质量地完成题目还需要一些实战技巧。4.1 环境熟悉与输入输出模板不同机试平台如牛客网、华为自己的OJ的输入输出方式可能有细微差别。上考场前务必用平台提供的练习场熟悉环境。C常用while (cin var)或while (getline(cin, str))。注意混合使用cin和getline时cin后的换行符可能会被getline读到需要用cin.ignore()清除缓冲区。JavaScanner是常用工具注意next()和nextLine()的区别。对于大量输入BufferedReader效率更高。JavaScript (Node.js)必须掌握readline模块的基本用法如上文所示。Pythonsys.stdin是标准输入流用for line in sys.stdin:循环读取是最通用的方法。建议为每种语言准备一个输入输出模板考试时直接套用节省时间。4.2 调试与自测策略机试环境通常不提供强大的IDE调试功能因此“打印调试”和“预先设计测试用例”至关重要。设计完备的测试用例在写代码前就把第2.2节提到的各种边界用例写在注释里。写完代码后立即用这些用例在脑子里跑一遍。正常用例”Hello World”首尾空格” Hello World “连续空格”Hello World”单个单词”Hello”,”a”带标点”Hello, World!”极端情况空字符串””纯空格” “长句子压力测试可选使用打印语句在关键步骤后打印中间变量如trim后的字符串、split得到的数组及其长度、总长度等。提交前记得注释掉或删除这些调试语句。利用本地IDE如果允许先在本地IDE中编写并通过所有测试用例再粘贴到考试系统。4.3 代码风格与评分要点华为OD机试通常有自动化评分系统但代码风格和健壮性也是隐形的考察点。函数封装像上面的示例一样将核心逻辑封装成一个函数如calculateAverageWordWeight。这使代码结构清晰易于测试也便于考官阅读。变量命名使用有意义的变量名totalLength,wordCount避免a,b,c。注释对关键步骤或易错点添加简短注释例如说明split参数为何用\\s。异常处理虽然本题简单但体现防御性编程思想是加分项。比如对除零的判断if wordCount 0。时间复杂度与空间复杂度本题最优解就是O(n)时间和O(m)空间或O(1)额外空间如果边遍历边计数。无需追求极致优化清晰第一。5. 从本题延伸的常见考点与复习建议“单词平均重量”本身不难但它串联起了多个基础考点。通过这道题我们可以梳理出OD机试中字符串和模拟类题目的常见考察方向字符串基本操作trim/strip,split,length/len, 遍历字符。数组/列表处理遍历、求和、统计。边界条件处理空输入、空格处理、除零错误。格式化输出保留小数位数。正则表达式基础对于Java/JS理解\s,\s的含义。复习建议夯实基础确保对所用语言的标准库字符串和数组操作函数了如指掌。比如Python的split()、Java的split(regex)、C的stringstream。刷题以练代学在牛客网、LeetCode上找类似的“字符串处理”题目进行练习例如统计字符出现次数字符串反转单词级、字符级验证回文串字符串转换整数 (atoi)最长公共前缀养成预判习惯每拿到一道题先花1-2分钟思考所有可能的边界情况并设计测试用例。这个习惯能帮你避免至少50%的失分。时间管理机试时间有限。像本题这样的简单题目标应该是10-15分钟内完成包括思考、编码、测试。把更多时间留给后面的中等或难题。这道“计算单词平均重量”的题目就像一块试金石。它考验的不是高深的算法而是程序员最基本的素养严谨、细致、对编程语言特性的熟悉以及将模糊需求转化为健壮代码的能力。把这些细节都做到位不仅能轻松拿下这道题的分值更能为应对OD机试中更复杂的挑战打下坚实的基础。