1. 问题重述与理解
LeetCode 3453题"分割正方形I"要求我们找到一个水平分割线,使得这条线以上所有正方形的总面积等于线以下所有正方形的总面积。题目给出了几个关键条件:
- 每个正方形由左下角坐标(xi, yi)和边长li表示
- 正方形可能与x轴平行(即边与坐标轴对齐)
- 正方形之间可能存在重叠,重叠区域需要多次计算
- 答案允许有10^-5的误差范围
这个问题本质上是在寻找一个y坐标值,使得所有正方形在这个y值以上的面积总和等于在y值以下的面积总和。由于正方形可能重叠,计算时需要特别注意重叠区域的处理。
2. 解题思路分析
2.1 暴力解法的问题
最直观的解法可能是遍历所有可能的y值,计算每个y值对应的上下面积,直到找到满足条件的y值。然而,这种方法存在几个问题:
- y的取值范围可能非常大(题目中提示0 <= yi <= 10^9)
- 需要极高的精度(误差不超过10^-5)
- 对于每个y值,都需要遍历所有正方形计算面积,时间复杂度为O(n)
因此,暴力解法在最坏情况下时间复杂度可能达到O(n*10^9),显然不可行。
2.2 二分查找的优势
二分查找是解决这类"在有序范围内寻找特定值"问题的理想选择。在这个问题中:
- y值的范围是有序的(从最小y到最大y+边长)
- 我们可以定义"y值是否足够大"的判断条件(即上面积是否大于总面积的一半)
- 二分查找可以将时间复杂度从O(n)降低到O(log n)
具体来说,我们可以:
- 先计算所有正方形的总面积
- 确定二分查找的初始范围(最小y和最大y+边长)
- 在每次迭代中计算中间y值对应的上面积
- 根据上面积与总面积一半的比较结果调整搜索范围
2.3 精度控制
题目要求答案误差不超过10^-5。我们可以通过两种方式保证精度:
- 设置二分查找的终止条件为区间长度小于10^-5
- 固定迭代次数(如50次),因为每次迭代将区间减半,50次迭代足以将初始区间(10^9)缩小到10^-15级别
第二种方法更为可靠,因为浮点数运算可能存在精度问题,固定迭代次数可以避免无限循环。
3. 算法实现细节
3.1 总面积计算
首先需要计算所有正方形的总面积。由于正方形可能重叠,总面积就是各个正方形面积的简单求和:
python复制total_area = sum(li * li for xi, yi, li in squares)
注意这里需要使用long long类型存储总面积,因为单个正方形面积可能达到(10^9)^2=10^18,多个这样的正方形相加可能超过普通整型的范围。
3.2 二分查找实现
二分查找的核心是check函数,它判断给定y值对应的上面积是否大于总面积的一半:
cpp复制bool check(double h) {
double upper_area = 0;
for (auto& s : squares) {
double from = max(double(s[1]), h);
double to = s[1] + s[2];
upper_area += max(0.0, (to - from) * s[2]);
}
return upper_area > half;
}
对于每个正方形,我们计算其在分割线h以上的部分面积:
- 正方形的上边界是yi + li
- 正方形在分割线以上的部分是从max(yi, h)到yi + li
- 高度为(yi + li - max(yi, h))
- 面积为高度乘以边长li
如果h >= yi + li,则正方形完全在分割线下方,贡献面积为0。
3.3 二分查找主循环
主循环执行固定次数的二分查找:
cpp复制double l = min_y, r = max_y;
for (int i = 0; i < 50; i++) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
return l;
这里使用50次迭代确保足够的精度。每次迭代将搜索区间减半,最终得到的l值就是满足条件的最小y坐标。
4. 复杂度分析
4.1 时间复杂度
算法的时间复杂度主要由两部分组成:
- 计算总面积:需要遍历所有正方形,O(n)
- 二分查找:每次迭代需要调用check函数遍历所有正方形,O(n)
迭代次数固定为50次,因此总复杂度为O(50n) = O(n)
4.2 空间复杂度
除了存储输入数据外,算法只需要常数级别的额外空间,因此空间复杂度为O(1)。
5. 边界条件与注意事项
5.1 正方形完全在分割线一侧
当分割线高于正方形的上边界(yi + li)时,该正方形对上面积无贡献;当分割线低于正方形的下边界(yi)时,正方形完全贡献给上面积。
5.2 精度问题
浮点数计算可能存在精度损失,特别是在面积很大的情况下。解决方案包括:
- 使用double类型存储面积和坐标
- 避免不必要的中间计算
- 使用固定迭代次数而非比较区间长度
5.3 初始范围选择
二分查找的初始范围应包含所有可能解:
- 最小y值:所有正方形的最小yi
- 最大y值:所有正方形的最大(yi + li)
但为了简化代码,可以直接使用[0, 1e9]作为初始范围,因为题目保证0 <= yi <= 1e9且1 <= li <= 1e9。
6. 代码实现与优化
6.1 完整C++实现
cpp复制class Solution {
private:
double half;
vector<vector<int>> squares;
bool check(double h) {
double total = 0;
for (auto& s : squares) {
double from = max(double(s[1]), h);
double to = s[1] + s[2];
total += max(0.0, (to - from) * s[2]);
}
return total > half;
}
public:
double separateSquares(vector<vector<int>>& squares) {
long long total = 0;
for (auto& s : squares) {
total += (long long)s[2] * s[2];
}
this->squares = squares;
half = total / 2.0;
double l = 0, r = 1e9;
for (int i = 0; i < 50; i++) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
return l;
}
};
6.2 优化点
- 提前计算并存储总面积的一半,避免重复计算
- 使用move语义传递squares数组,减少拷贝开销(但LeetCode的judge可能不支持)
- 使用固定迭代次数而非动态判断精度,保证最坏情况下的性能
7. 测试用例验证
7.1 示例1验证
输入:[[0,0,1],[2,2,1]]
预期输出:1.00000
验证:
- 总面积 = 1 + 1 = 2
- 一半面积 = 1
- y=1时:
- 第一个正方形:from=max(0,1)=1, to=1, 上面积=0
- 第二个正方形:from=max(2,1)=2, to=3, 上面积=1
- 总上面积=1,等于一半面积
- 因此最小y=1正确
7.2 示例2验证
输入:[[0,0,2],[1,1,1]]
预期输出:1.16667
验证:
- 总面积 = 4 + 1 = 5
- 一半面积 = 2.5
- y≈1.16667时:
- 第一个正方形:from≈1.16667, to=2, 上面积≈(2-1.16667)*2≈1.66666
- 第二个正方形:from≈1.16667, to=2, 上面积≈(2-1.16667)*1≈0.83333
- 总上面积≈2.5,等于一半面积
- 计算结果与预期一致
8. 常见问题与解决
8.1 为什么使用50次迭代?
50次迭代可以将初始区间[0,1e9]缩小到1e9/2^50 ≈ 8.8e-16的精度,远高于题目要求的1e-5。这是一种保守但可靠的做法。
8.2 如何处理正方形重叠?
题目明确说明重叠区域应多次计算,因此我们只需要独立计算每个正方形在分割线上方的面积,无需考虑重叠问题。
8.3 为什么二分查找能找到最小y值?
因为我们将check函数定义为"上面积是否大于一半",当check为true时说明分割线太低,需要提高;为false时说明分割线可能足够高或太高。通过不断调整,最终会收敛到满足条件的最小y值。
9. 算法扩展思考
9.1 其他形状的扩展
如果题目中的形状不是正方形,而是其他多边形,算法需要如何调整?
- 需要能够计算任意多边形在给定分割线上方的面积
- 可能需要更复杂的几何计算
- 但二分查找的思路仍然适用
9.2 三维情况
如果在三维空间中寻找分割平面,使平面两侧体积相等:
- 需要计算每个物体在平面上方的体积
- 可能需要三重积分等复杂计算
- 二分查找仍然可以应用,但计算成本会大幅增加
9.3 多个分割线
如果需要多条分割线将区域分成多个等面积部分:
- 可以顺序应用二分查找
- 每次找到一个分割线后,将问题分解为子问题
- 复杂度会随分割线数量线性增加
10. 实际应用场景
这种类型的算法在实际中有多种应用:
- 图像处理中的阈值选择(如Otsu算法)
- 物理模拟中的质心计算
- 统计学中的中位数查找(可以看作是一维版本)
- 资源分配中的公平分割问题
理解这类问题的解法有助于解决许多实际工程中的分割和平衡问题。