1. 项目概述与核心价值
最近在技术社区和求职圈里,“华为OD机试”的热度一直居高不下,尤其是那些经典的算法真题,几乎成了大家备考的“必刷题库”。今天要拆解的这道“矩形相交的面积”,就是其中一道非常典型、也极具代表性的题目。它看似简单,就是一个计算两个矩形重叠部分面积的问题,但如果你真把它当成一道简单的几何题,那可能就错过了它背后考察的算法思维和编程基本功。这道题频繁出现在华为OD的B卷乃至其他大厂的笔试中,不是没有道理的。它完美地融合了基础的数学逻辑、严谨的边界条件判断,以及对编程语言数据结构(如坐标表示)的熟练运用,是检验一个程序员是否具备扎实基础和缜密思维的绝佳试金石。
无论你是正在备战华为OD机考、校招笔试的应届生,还是希望巩固算法基础的开发者,这道题都值得你花时间深入研究。它不仅教你如何计算相交面积,更重要的是,它训练你如何将现实世界的几何问题,抽象成计算机可以处理的逻辑和代码,并在这个过程中,规避掉所有可能的“坑”。接下来,我会从问题本质、多种思路对比、到不同语言(C++、Java、Python、C、JS)的具体实现与细节剖析,为你呈现一份完整的“解题报告”。
2. 问题本质与数学模型抽象
2.1 问题重述与输入输出规范
题目通常这样描述:在二维平面直角坐标系中,给定两个矩形的信息。每个矩形通过其左下角顶点坐标(x1, y1)和右上角顶点坐标(x2, y2)来确定。要求计算这两个矩形重叠部分的面积。如果两个矩形没有重叠,则重叠面积为0。
这是一个非常清晰的定义。关键在于,我们需要从这8个数字(两个矩形,每个矩形两个点,每个点两个坐标)中,推导出重叠矩形是否存在以及其大小。
输入格式(常见): 一行或多行输入,包含8个整数,分别代表:x1 y1 x2 y2 x3 y3 x4 y4其中(x1, y1)和(x2, y2)是第一个矩形的左下角和右上角坐标。(x3, y3)和(x4, y4)是第二个矩形的左下角和右上角坐标。 通常保证x1 < x2,y1 < y2,x3 < x4,y3 < y4,即输入的坐标是有效的矩形。
输出格式: 一个整数,表示相交部分的面积。
2.2 核心数学模型:投影与一维区间相交
这是理解本题的关键。一个矩形在二维平面的相交,可以分解为在两个独立维度(X轴和Y轴)上一维线段相交的问题。
- X轴投影:将两个矩形投影到X轴上,得到两个线段区间。
- 矩形A:
[x1, x2] - 矩形B:
[x3, x4]
- 矩形A:
- Y轴投影:将两个矩形投影到Y轴上,得到两个线段区间。
- 矩形A:
[y1, y2] - 矩形B:
[y3, y4]
- 矩形A:
两个矩形相交,当且仅当它们在X轴上的投影线段相交并且在Y轴上的投影线段相交。
那么,如何计算一维线段的相交长度呢? 对于两个区间[a1, a2]和[b1, b2](假设a1 < a2,b1 < b2):
- 首先判断是否相交:相交的条件是
max(a1, b1) < min(a2, b2)。如果这个条件不成立,说明两个区间没有重叠部分。 - 如果相交,重叠部分的长度就是
min(a2, b2) - max(a1, b1)。
将这个原理应用到两个维度上:
- 相交部分在X轴上的宽度
width = max(0, min(x2, x4) - max(x1, x3)) - 相交部分在Y轴上的高度
height = max(0, min(y2, y4) - max(y1, y3)) - 相交面积
area = width * height
公式中的max(0, ...)非常巧妙。当min(x2, x4) <= max(x1, x3)时,说明在X轴上没有重叠,此时min(x2, x4) - max(x1, x3)结果为负数或零。max(0, ...)会将其修正为0,意味着宽度为0。高度计算同理。最终面积width * height如果任一维度为0,结果就是0,完美处理了不相交的情况。
注意:这里有一个初学者极易混淆的点。矩形的坐标是
(左下x, 左下y, 右上x, 右上y)。在计算宽度时,我们用的是x2 - x1,这本身就是正数。但在计算重叠宽度时,我们比较的是两个矩形右边界的最小值 (min(x2, x4)) 和两个矩形左边界的最大值 (max(x1, x3))。重叠部分左边界是两者左边界的靠右者,右边界是两者右边界的靠左者。想通这一点,整个公式就豁然开朗了。
3. 算法思路详解与对比
基于上述数学模型,我们可以衍生出几种实现思路,它们核心一致,但在代码组织和判断逻辑上略有不同。
3.1 思路一:直接公式法(推荐)
这是最简洁、最高效,也是最符合数学直觉的方法。直接套用上一节推导出的公式。算法步骤:
- 读取两个矩形的8个坐标值。
- 计算重叠部分的左下角坐标
(inter_x1, inter_y1)和右上角坐标(inter_x2, inter_y2)。inter_x1 = max(x1, x3)inter_y1 = max(y1, y3)inter_x2 = min(x2, x4)inter_y2 = min(y2, y4)
- 判断重叠矩形是否有效。
- 如果
inter_x1 < inter_x2且inter_y1 < inter_y2,则矩形有效,面积 =(inter_x2 - inter_x1) * (inter_y2 - inter_y1)。 - 否则,面积 = 0。
- 如果
这个思路和之前的公式本质相同,只是先显式地求出了“相交矩形”的坐标,再判断其有效性。可读性更强。
3.2 思路二:分情况讨论法
这是一种更“朴素”的思维,通过几何位置关系来枚举。考虑两个矩形在X轴和Y轴上的相对位置,可以分出很多种情况(完全分离、包含、相交于边、相交于角等)。虽然逻辑上可行,但代码会非常冗长,容易遗漏边界情况(比如刚好相切算不算相交?题目通常要求重叠面积大于0才算相交,相切面积为0),因此不推荐在编程题中使用。但它有助于在纸上画图理解所有可能性。
3.3 思路三:利用“排斥”原理
计算两个矩形围成的“外包络矩形”面积,然后减去两个矩形单独的面积,再加上可能的重叠面积?不对,这是计算并集面积的容斥原理。对于交集,直接使用公式法或思路一才是最直接的。
对比与选择: 对于机试或笔试,强烈推荐使用“直接公式法”或“思路一”。它们代码量少,逻辑清晰,运行效率高(O(1)时间复杂度),且不易出错。接下来的代码实现部分,将主要围绕这种思路展开。
4. 多语言代码实现与深度解析
我们将用五种常见的编程语言(C++, Java, Python, C, JavaScript)来实现“直接公式法”,并针对每种语言的特点和机试中的注意事项进行剖析。
4.1 C++ 实现
#include <iostream> #include <algorithm> // 用于 max 和 min 函数 using namespace std; int main() { int x1, y1, x2, y2, x3, y3, x4, y4; // 假设输入格式为空格分隔的8个整数 cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> x4 >> y4; // 计算相交矩形的潜在边界 int inter_left = max(x1, x3); int inter_bottom = max(y1, y3); int inter_right = min(x2, x4); int inter_top = min(y2, y4); // 计算宽度和高度,如果无重叠则结果为负数或零,用max处理为0 int width = max(0, inter_right - inter_left); int height = max(0, inter_top - inter_bottom); // 面积 = 宽 * 高 int area = width * height; cout << area << endl; return 0; }C++ 实现要点解析:
- 头文件:
<algorithm>提供了max和min函数,比手写条件判断更简洁。 - 输入:使用
cin进行标准输入,这是机试中最常见的方式。务必确保变量读取顺序与题目输入格式一致。 - 核心计算:
inter_left = max(x1, x3)获取重叠部分左边界(两个左边界中更大的那个)。inter_right = min(x2, x4)获取重叠部分右边界(两个右边界中更小的那个)。宽度即为两者之差。 - 边界处理:
max(0, inter_right - inter_left)是关键。当两个矩形在X轴上分离时,inter_right - inter_left为负,max(0, ...)将其修正为0,表示没有重叠宽度。高度同理。 - 输出:直接输出
area。
踩坑提醒:在有的在线判题系统(OJ)中,可能要求处理多组测试数据。上述代码只处理了一组。如果题目说明包含多组数据,通常需要用
while(cin >> x1 >> y1 ...)这样的循环来读取,直到文件结束(EOF)。
4.2 Java 实现
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读取8个整数 int x1 = scanner.nextInt(); int y1 = scanner.nextInt(); int x2 = scanner.nextInt(); int y2 = scanner.nextInt(); int x3 = scanner.nextInt(); int y3 = scanner.nextInt(); int x4 = scanner.nextInt(); int y4 = scanner.nextInt(); scanner.close(); // 良好习惯,关闭Scanner // 计算相交矩形边界 int interLeft = Math.max(x1, x3); int interBottom = Math.max(y1, y3); int interRight = Math.min(x2, x4); int interTop = Math.min(y2, y4); // 计算宽高,利用Math.max确保非负 int width = Math.max(0, interRight - interLeft); int height = Math.max(0, interTop - interBottom); // 计算面积 int area = width * height; System.out.println(area); } }Java 实现要点解析:
- Scanner类:这是最常用的控制台输入工具。注意
nextInt()方法会读取下一个整数。 - Math工具类:
Math.max()和Math.min()是静态方法,直接使用。 - 资源管理:使用完
Scanner后调用close()是一个好习惯,虽然在简单的单次程序中可以省略,但在复杂或需要及时释放资源的场景下很重要。 - 代码风格:Java 变量命名通常采用驼峰式。算法逻辑与C++版本完全一致。
常见问题:在华为OD的机试环境中,Java类名必须为
Main。务必注意大小写。此外,有些OJ对Java的内存和时间限制比较严格,但这种O(1)的算法完全不用担心。
4.3 Python 实现
def main(): # 读取一行输入,分割成字符串列表,再转换为整数列表 data = list(map(int, input().split())) if len(data) != 8: # 可选:处理输入格式错误,但题目通常保证正确 return x1, y1, x2, y2, x3, y3, x4, y4 = data # 计算相交矩形边界 inter_left = max(x1, x3) inter_bottom = max(y1, y3) inter_right = min(x2, x4) inter_top = min(y2, y4) # 计算宽度和高度,如果为负则取0 width = max(0, inter_right - inter_left) height = max(0, inter_top - inter_bottom) # 计算面积 area = width * height print(area) if __name__ == "__main__": main()Python 实现要点解析:
- 灵活的输入处理:
input().split()读取一行并按空格分割。map(int, ...)将每个字符串映射为整数。list(...)转换为列表。最后使用序列解包赋值给8个变量。这是一行处理多个输入的常用技巧。 - 内置函数:Python的
max()和min()是内置函数,可以直接使用,非常方便。 - 简洁性:Python代码通常非常简洁,逻辑一目了然。
width = max(0, inter_right - inter_left)这行代码完美体现了Python的优雅。 - 入口检查:
if __name__ == "__main__":是Python脚本的标准入口写法,确保模块被直接运行时才执行main()函数。
效率提示:对于这种简单计算,Python的实现速度完全足够。但在处理海量数据或复杂循环时,需要注意Python的运行效率可能低于C++/Java。本题不涉及。
4.4 C 语言实现
#include <stdio.h> // 自定义max和min函数,因为C标准库没有直接提供 int max(int a, int b) { return (a > b) ? a : b; } int min(int a, int b) { return (a < b) ? a : b; } int main() { int x1, y1, x2, y2, x3, y3, x4, y4; // 读取输入 scanf("%d %d %d %d %d %d %d %d", &x1, &y1, &x2, &y2, &x3, &y3, &x4, &y4); // 计算相交矩形边界 int inter_left = max(x1, x3); int inter_bottom = max(y1, y3); int inter_right = min(x2, x4); int inter_top = min(y2, y4); // 计算宽度和高度 int width = inter_right - inter_left; int height = inter_top - inter_bottom; // 判断并计算面积 int area = 0; if (width > 0 && height > 0) { area = width * height; } // 如果width或height <= 0,area保持为0 printf("%d\n", area); return 0; }C 语言实现要点解析:
- 自定义工具函数:C语言标准库没有
max/min函数,需要自己实现。这里使用了三元运算符? :,简洁高效。 - 输入输出:使用
scanf和printf,注意scanf需要传递变量的地址(&运算符)。 - 逻辑判断:C语言版本没有使用
max(0, width)的技巧,而是先计算出width和height,然后通过if (width > 0 && height > 0)来判断是否真正相交。这种写法更符合C语言的直白风格,逻辑同样清晰。 - 效率:C语言的实现通常效率最高,但代码量稍多。对于算法题,清晰性比极致的微优化更重要。
边界情况:这里判断条件是
width > 0 && height > 0。如果题目定义两个矩形边重合(即width == 0或height == 0)不算相交,那么这个判断是正确的。如果题目定义重合也算相交(面积为0),那么条件应该改为width >= 0 && height >= 0。通常机试题默认是“有重叠区域”才算相交,即面积大于0,所以>的判断是安全的。务必仔细审题。
4.5 JavaScript (Node.js) 实现
假设在华为OD的机试环境(或其他支持Node.js的OJ)中,我们需要处理控制台输入。
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on('line', (input) => { // 将输入行按空格分割,并转换为数字 const numbers = input.trim().split(/\s+/).map(Number); if (numbers.length !== 8) { return; // 输入格式错误处理 } const [x1, y1, x2, y2, x3, y3, x4, y4] = numbers; // 计算相交矩形边界 const interLeft = Math.max(x1, x3); const interBottom = Math.max(y1, y3); const interRight = Math.min(x2, x4); const interTop = Math.min(y2, y4); // 计算宽度和高度 const width = Math.max(0, interRight - interLeft); const height = Math.max(0, interTop - interBottom); // 计算面积 const area = width * height; console.log(area); // 如果只有一行输入,可以关闭rl。如果是多行且这是最后一行,也需要处理。 // rl.close(); }); // 注意:对于单次输入,上述代码足够。如果题目是多组数据,需要累积处理或每行独立计算。JavaScript 实现要点解析:
- 输入模块:Node.js中使用
readline模块来逐行读取标准输入。这是处理算法题输入的标准方式。 - 数据处理:
input.trim().split(/\s+/).map(Number)是一个经典组合:去除首尾空格,按一个或多个空白字符分割,然后将每个部分转换为数字。 - 解构赋值:
const [x1, y1, ...] = numbers是ES6的语法,能优雅地将数组元素赋值给变量。 - Math对象:和Java一样,使用
Math.max()和Math.min()。 - 异步事件:
rl.on('line', ...)是事件监听器,每读到一行数据就会触发回调函数。对于单次输入,在回调函数里计算并输出即可。对于多次输入,可能需要将rl.close()放在合适的时机,或者累积数据。
环境差异:不同的OJ对JavaScript的支持可能不同。有的可能要求使用
console.log输出,有的可能要求将结果作为函数返回值。华为OD的机试环境通常会有明确的题目输入输出说明,请务必按照平台要求调整代码框架。例如,有时可能需要写一个function solve(input)之类的函数。
5. 边界条件与测试用例设计
一道题能否AC(Accepted),往往取决于是否考虑了所有边界条件。对于“矩形相交的面积”,以下测试用例至关重要:
| 测试用例描述 | 输入样例 (x1 y1 x2 y2 x3 y3 x4 y4) | 预期输出 | 验证点 |
|---|---|---|---|
| 常规相交 | 0 0 2 2 1 1 3 3 | 1 | 标准相交,面积1 |
| 包含关系 | 0 0 5 5 1 1 3 3 | 4 | 一个矩形完全在另一个内部 |
| 不相交(分离) | 0 0 1 1 2 2 3 3 | 0 | 完全分离 |
| 相切于边 | 0 0 2 2 2 0 4 2 | 0 | X轴上右边界与左边界重合,无重叠面积 |
| 相切于点 | 0 0 2 2 2 2 4 4 | 0 | 右上角与左下角重合,无重叠面积 |
| 负坐标相交 | -2 -2 0 0 -1 -1 1 1 | 1 | 坐标包含负数,逻辑不变 |
| 大整数 | 0 0 1000000 1000000 500000 500000 1500000 1500000 | 250000000000 | 验证是否使用int足够(面积可能超出32位int范围?) |
| 退化矩形? | 0 0 0 5 1 1 3 3 | 0 | 第一个矩形宽度为0,不是有效矩形,但题目通常保证输入有效,此用例可测鲁棒性 |
关于数据类型的选择: 从最后一个“大整数”用例可以看出,当坐标值很大时,宽度和高度的差值可能很大,相乘后的面积可能超出普通int(32位有符号,最大值约21亿)的范围。在C++、Java中,可以使用long long(C++) 或long(Java) 来存储面积。在Python中,整数是任意精度的,无需担心。在C语言中,可以使用long long。在JS中,使用Number(双精度浮点),但整数在安全范围内(2^53以下)是精确的,对于机试通常足够。
修正后的C++版本(考虑大数):
#include <iostream> #include <algorithm> using namespace std; int main() { long long x1, y1, x2, y2, x3, y3, x4, y4; // 使用long long cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> x4 >> y4; long long inter_left = max(x1, x3); long long inter_bottom = max(y1, y3); long long inter_right = min(x2, x4); long long inter_top = min(y2, y4); long long width = max(0LL, inter_right - inter_left); // 注意0LL long long height = max(0LL, inter_top - inter_bottom); long long area = width * height; cout << area << endl; return 0; }6. 常见错误与调试技巧
在实现这道题时,新手常会遇到以下几个问题:
坐标顺序混淆:误将
(x1, y1)和(x2, y2)当成左上角和右下角。务必确认题目描述,本题是左下角和右上角。如果题目给的是左上和右下,公式需要调整(重叠部分上边界是min(y1, y3),下边界是max(y2, y4),因为Y轴向下为正)。边界条件判断错误:
- 错误写法:
if (x3 < x2 && x4 > x1 && ...)这种判断条件非常容易遗漏或写错边界。 - 正确做法:坚持使用
max和min推导重叠边界,然后用if (width > 0 && height > 0)或max(0, ...)来判断。这是最不容易出错的方法。
- 错误写法:
数据类型溢出:如第5节所述,当坐标值很大时,中间结果和最终面积可能溢出32位整数。在不确定的情况下,统一使用64位整数(
long long/long)是更安全的选择。输入格式处理不当:机试系统输入可能是空格分隔,也可能是换行分隔。我们的代码通常按空格分隔读取 (
cin >>,scanf(“%d”),input().split())。如果题目明确是换行分隔,可能需要多次读取。仔细阅读题目输入说明。多组测试数据未处理:有些题目会说明“输入包含多组测试数据”,代码需要用循环包裹核心逻辑,直到读取到文件结束符(EOF)。例如:
// C++ 多组数据示例 int x1, y1, x2, y2, x3, y3, x4, y4; while (cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> x4 >> y4) { // ... 计算并输出面积 ... }
调试技巧:
- 画图:当不确定时,在纸上画出两个矩形的相对位置,标出坐标,手动计算重叠区域。这是最直观的调试方法。
- 打印中间变量:在代码中打印出计算出的
inter_left,inter_right,width等中间结果,看是否符合预期。 - 使用第5节的测试用例:逐一测试,特别是边界用例,确保都能通过。
7. 举一反三与相关题型拓展
掌握了矩形相交,你可以轻松解决一系列相关的几何和算法问题:
矩形合并(Union)面积:计算两个矩形覆盖的总面积。可以用容斥原理:
面积A + 面积B - 相交面积。核心仍然是计算相交面积。多个矩形相交:给定多个矩形,判断其中任意两个是否相交,或者求所有矩形的公共相交区域(可能不存在)。对于公共区域,可以转化为求所有矩形在X轴投影的交集和Y轴投影的交集。即:
公共左边界 = max(所有矩形左边界)公共右边界 = min(所有矩形右边界)公共下边界 = max(所有矩形下边界)公共上边界 = min(所有矩形上边界)然后判断width和height是否大于0。轴对齐矩形(AABB)的碰撞检测:在游戏开发或图形学中,判断两个轴对齐的包围盒(AABB)是否碰撞,其原理和本题一模一样。这是最基础的碰撞检测算法。
LeetCode 相关题目:
- 223. 矩形面积:计算两个矩形覆盖的总面积,直接应用容斥原理。
- 836. 矩形重叠:判断两个矩形是否重叠,是本题的简化版,只需判断是否相交,无需计算面积。
- 850. 矩形面积 II:计算多个矩形覆盖的总面积,难度较大,需要用到扫描线算法。
从2D推广到3D:在三维空间中,判断两个轴对齐的立方体(AABB)是否相交,原理完全一致,只是多了一个Z维度。相交当且仅当在X、Y、Z三个轴上的投影区间都相交。
这道“矩形相交的面积”题,就像一把钥匙,帮你打开了计算几何中“轴对齐边界框”问题的大门。它的核心思想——将高维问题分解为多个一维独立子问题——是一种非常重要的算法思维。在华为OD乃至其他技术面试中,展现出对这种基础问题深刻、清晰且严谨的理解,远比死记硬背复杂的算法更能体现你的基本功和思维能力。