1. 问题分析与建模
1.1 题目理解与关键点提取
这道题目要求我们找到一个水平分割线y=k,使得所有正方形在该线上方的总面积等于线下方的总面积。题目给出了几个关键约束条件:
- 正方形都是与x轴平行的,由左下角坐标(xi,yi)和边长li确定
- 正方形之间可能存在重叠区域,重叠部分需要重复计算
- 答案精度要求误差不超过10^-5
- 输入规模可能达到5×10^4个正方形
从示例分析可以看出,当分割线穿过正方形时,需要精确计算被分割的面积。例如在示例2中,红色正方形(边长2)被y=1.16667分割,线上部分占5/6,线下部分占7/6。
1.2 数学建模与性质分析
设总面积为Stotal,我们需要找到最小的y=k使得线下面积Sbelow(k)=Stotal/2。通过分析可以发现:
- Sbelow(k)是关于k的非递减函数:当k增大时,线下面积不会减少
- Sbelow(k)是连续函数:当k变化时,线下面积不会突变
- 当k=0时Sbelow(0)=0,当k=max(yi+li)时Sbelow=Stotal
这些性质表明我们可以使用二分查找来高效地找到满足条件的k值,因为函数具有单调性且连续,保证了二分查找的正确性。
2. 算法设计与优化
2.1 二分查找框架设计
基于上述分析,我们采用二分查找算法框架:
- 确定搜索范围:left=0,right=max(yi+li)
- 计算总面积Stotal
- 进行二分迭代:
- mid = (left + right)/2
- 计算Sbelow(mid)
- 比较Sbelow(mid)与Stotal/2
- 调整左右边界
- 当区间足够小时返回结果
注意:由于题目要求精度为10^-5,我们需要确保二分查找的迭代次数足够。47次迭代可以保证区间长度小于2^-47≈7×10^-15,远高于精度要求。
2.2 面积计算优化
计算Sbelow(k)时需要遍历所有正方形,对每个正方形计算其在线下的面积贡献:
- 若k ≤ yi:贡献为0
- 若k ≥ yi+li:贡献为li²
- 若yi < k < yi+li:贡献为li×(k-yi)
这个计算过程时间复杂度为O(n),是算法的主要耗时部分。由于n可能很大(5×10^4),我们需要确保计算高效:
- 使用基本数据类型而非对象,减少内存开销
- 避免不必要的对象创建和函数调用
- 使用累加而非集合操作
3. 代码实现详解
3.1 主函数逻辑
java复制class Solution {
public double separateSquares(int[][] squares) {
// 计算总正方形面积和最大y坐标
long totalArea = 0;
int maxTopY = 0;
for (int[] square : squares) {
int sideLength = square[2];
int bottomY = square[1];
totalArea += (long) sideLength * sideLength;
maxTopY = Math.max(maxTopY, bottomY + sideLength);
}
// 二分查找初始化
double left = 0;
double right = maxTopY;
// 固定47次迭代保证精度
for (int i = 0; i < 47; i++) {
double mid = (left + right) / 2;
if (isLowerAreaEnough(squares, mid, totalArea)) {
right = mid;
} else {
left = mid;
}
}
return (left + right) / 2;
}
// ... 其他方法
}
3.2 面积计算函数
java复制private boolean isLowerAreaEnough(int[][] squares, double currentY, long totalArea) {
double lowerArea = 0.0;
for (int[] square : squares) {
int squareBottomY = square[1];
int sideLength = square[2];
if (squareBottomY < currentY) {
double validHeight = Math.min(currentY - squareBottomY, (double) sideLength);
lowerArea += sideLength * validHeight;
}
}
return lowerArea >= totalArea / 2.0;
}
关键实现细节:
- 使用long类型存储总面积防止整数溢出
- 在面积计算时,currentY-squareBottomY可能超过边长,需要用Math.min限制
- 直接累加面积而非存储中间结果,节省内存
4. 复杂度分析与边界情况
4.1 时间复杂度分析
- 预处理阶段:O(n)计算总面积和最大y坐标
- 二分查找:47次迭代,每次O(n)的面积计算
- 总时间复杂度:O(n)(因为47是常数)
4.2 空间复杂度分析
- 仅使用常数额外空间:O(1)
- 输入数据本身占用O(n)空间
4.3 边界情况处理
- 所有正方形完全重叠:算法仍然适用,因为面积计算会累加所有重叠部分
- 极大正方形包含其他所有正方形:maxTopY会正确设置为最大正方形的上边界
- 正方形紧贴y=0:left初始化为0可以正确处理
- 极小正方形(li=1):精度由二分查找保证
5. 算法优化与变种
5.1 迭代次数优化
虽然47次迭代足够,但可以根据实际数据范围动态计算所需迭代次数:
java复制int iterations = (int) Math.ceil(Math.log((maxTopY) / 1e-5) / Math.log(2));
这样可以减少不必要的迭代,但对性能提升有限。
5.2 并行计算优化
对于极大n的情况,可以将面积计算并行化:
java复制lowerArea = Arrays.stream(squares)
.parallel()
.mapToDouble(square -> {
// 计算单个正方形的贡献
})
.sum();
但需要注意并行化的开销可能超过收益,需要实际测试。
5.3 三维扩展思考
类似问题可以扩展到三维空间,寻找分割平面使得立方体的体积上下相等。此时:
- 分割平面可以是任意方向的
- 体积计算更为复杂
- 可能需要使用更高级的数值方法
6. 实际应用与类似问题
6.1 实际应用场景
这类分割问题在实际中有多种应用:
- 图像处理中的阈值选择
- 物理系统中的平衡点计算
- 统计学中的中位数查找
- 资源分配中的公平分割
6.2 LeetCode类似题目
-
- 分割数组的最大值:也是二分查找应用
-
- 爱吃香蕉的珂珂:在单调函数中查找特定值
-
- 在D天内送达包裹的能力:类似的二分查找框架
6.3 算法模式总结
这类问题的通用解法模式:
- 确定单调函数关系
- 确定搜索范围
- 设计检查函数
- 应用二分查找
- 处理精度要求
掌握这个模式可以解决一大类优化问题。