华为OD机试中的“磁盘容量排序”题目是一道考察应聘者编程能力和算法理解的经典题目。以下是对这道题目的详细解析:
一、题目描述
磁盘的容量单位常用的有M,G,T这三个等级,它们之间的换算关系为1T=1024G,1G=1024M,现在给定n块磁盘的容量,请对它们按从小到大的顺序进行稳定排序只,例如给定5块盘的容量,1T,20M,3G,10G6T,3M12G9M排序后的结果为20M,3G,3M12G9M,1T,10G6T。注意单位可以重复出现,上述3M12G9M表示的容量即为3M+12G+9M,和12M12G相等。
二、输入描述
- 输入第一行包含一个整数n(2<=n<= 100),表示磁盘的个数。
- 接下的n行,每行一个字符串(长度大于2,小于30),表示磁盘的容量,由一个或多个格式为mv的子串组成,其中m表示容量大小,v表示容量单位,例如20M,1T,30G,10G6T,3M12G9M。
- 磁盘容量m的范围为1到1024的正整数,容量单位v的范围只包含题目中提到的M,G,T三种,换算关系如题目描述。
三、输出描述
输出n行,表示n块磁盘容量排序后的结果。
四、解题思路
-
解析输入:
- 首先读取输入的整数
n
,它表示磁盘的个数。 - 接着读取接下来的
n
行,每行包含一个表示磁盘容量的字符串。
- 首先读取输入的整数
-
解析容量字符串:
- 对于每个容量字符串,我们需要将其拆分为多个
mv
子串,其中m
是容量大小,v
是容量单位。 - 将每个子串的容量大小乘以对应的单位换算系数(M=1, G=1024, T=1024^2),然后将所有结果相加,得到该磁盘的总容量(以M为单位)。
- 对于每个容量字符串,我们需要将其拆分为多个
-
存储和排序:
- 使用一个列表来存储每个磁盘的原始字符串和计算出的总容量(以M为单位)。
- 使用Java的
Collections.sort
方法对列表进行排序,排序时根据总容量进行比较,但保留原始字符串以便输出。
-
输出排序结果:
- 按照排序后的顺序输出每个磁盘的原始容量字符串。
五、代码实现
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
* 磁盘容量解析和排序工具类
*/
class DiskCapacity {
// 换算系数
private static final int M = 1; // 兆字节
private static final int G = 1024; // 吉字节
private static final int T = 1024 * 1024; // 太字节
/**
* 解析容量字符串并返回总容量(以兆字节为单位)
* 支持的单位有:M(兆字节)、G(吉字节)、T(太字节)
* @param capacityStr 容量字符串
* @return 总容量(以兆字节为单位)
* @throws NumberFormatException 如果输入字符串格式不正确
*/
private static int parseCapacity(String capacityStr) {
int totalCapacityInM = 0;
// 根据单位和数字边界分割字符串
String[] parts = capacityStr.split("(?<=[MGT])\\s*");
for (String part : parts) {
// 跳过空字符串
if (part.isEmpty()) continue;
// 提取数字部分
int size = Integer.parseInt(part.substring(0, part.length() - 1));
// 提取单位部分
char unit = part.charAt(part.length() - 1);
switch (unit) {
case 'M':
totalCapacityInM += size * M;
break;
case 'G':
totalCapacityInM += size * G;
break;
case 'T':
totalCapacityInM += size * T;
break;
default:
throw new NumberFormatException("未知的单位: " + unit);
}
}
return totalCapacityInM;
}
/**
* 主函数用于读取用户输入的磁盘容量字符串,解析并按容量排序后输出
* @param args 命令行参数,未使用
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine(); // 消耗掉换行符
// 创建一个列表用于存储容量信息及其原始字符串表示
List<Map.Entry<Integer, String>> capacities = new ArrayList<>();
// 遍历输入,读取n次以收集所有容量信息
for (int i = 0; i < n; i++) {
// 读取下一行输入作为容量的字符串表示
String capacityStr = scanner.nextLine();
try {
// 解析容量字符串为整数,表示总容量(以M为单位)
int totalCapacityInM = parseCapacity(capacityStr);
// 将解析后的容量及其原始字符串添加到列表中
capacities.add(new AbstractMap.SimpleEntry<>(totalCapacityInM, capacityStr));
} catch (NumberFormatException e) {
// 处理解析错误,打印无效输入的错误信息
System.err.println("无效的输入: " + capacityStr + ", 错误: " + e.getMessage());
}
}
// 使用稳定排序(例如TimSort,Java的Arrays.sort和Collections.sort都使用它)
capacities.sort(Map.Entry.comparingByKey());
// 输出排序后的结果
for (Map.Entry<Integer, String> entry : capacities) {
System.out.println(entry.getValue());
}
scanner.close();
}
}
六、运行示例解析
输入:
5
1T
20M
3G
10G6T
3M12G9M
解析:
1T
解析为1 * 1024^2 = 1048576M
20M
解析为20 * 1 = 20M
3G
解析为3 * 1024 = 3072M
10G6T
解析为(10 * 1024) + (6 * 1024^2) = 6291440 + 6291456 = 12582896M
3M12G9M
解析为(3 * 1) + (12 * 1024) + (9 * 1) = 3 + 12288 + 9 = 12297 + 3 = 12300M
(注意这里我们合并了所有的M、G单位)
排序:
- 按总容量(以M为单位)排序:
20M
,3072M (3G)
,12300M (3M12G9M)
,1048576M (1T)
,12582896M (10G6T)
输出:
20M
3G
3M12G9M
1T
10G6T
这个输出与题目要求的稳定排序结果一致。注意,在解析3M12G9M
时,我们将其视为3M + 12G + 9M
,并正确地将所有单位转换为M以进行比较。
七、扩展与注意事项
-
输入验证:
- 在实际应用中,应增加对输入的验证,如检查输入字符串是否符合格式要求,是否包含非法字符等。
- 可以使用正则表达式或字符串处理方法进行验证。
-
异常处理:
- 在解析容量字符串时,应捕获并处理可能的异常,如
NumberFormatException
等。 - 在主函数中,应捕获并处理可能发生的异常,以确保程序的健壮性。
- 在解析容量字符串时,应捕获并处理可能的异常,如
-
性能优化:
- 如果输入的磁盘数量非常大,可以考虑使用更高效的排序算法或数据结构来优化性能。
- 在解析容量字符串时,可以使用更高效的字符串分割方法或正则表达式匹配技术来提高解析速度。
-
代码可读性:
- 在编写代码时,应注重代码的可读性和可维护性。
- 可以使用注释、变量命名和代码结构等方式来提高代码的可读性。
-
扩展性:
- 如果需要支持更多的容量单位或更复杂的容量表示方式(如小数表示、科学计数法等),可以对
parseCapacity
方法进行扩展和修改。 - 可以将解析和排序功能封装为独立的类或方法,以便在其他项目中复用。
- 如果需要支持更多的容量单位或更复杂的容量表示方式(如小数表示、科学计数法等),可以对
-
正则表达式的应用:
- 一定要注意正则表达式的应用否则
String[] parts = capacityStr.split("(?<=[MGT])\\s*");
这里应用不对,很容易出错。