1. 问题重述与理解
LeetCode 2943题"最大化网格图中正方形空洞的面积"是一个有趣的网格操作问题。题目描述如下:
我们有一个由n+2条横线和m+2条竖线组成的网格图,初始状态下所有网格单元都是1×1大小。所有线段的编号从1开始。
给定:
- 整数n和m
- 横线移除候选数组hBars(包含[2,n+1]区间内互不相同的横线编号)
- 竖线移除候选数组vBars(包含[2,m+1]区间内互不相同的竖线编号)
我们可以选择移除hBars和vBars中的部分线段(也可以不移除任何线段),目标是使剩余网格图中形成的最大正方形空洞面积尽可能大。正方形空洞指的是正方形内部不含有任何线段。
2. 解题思路分析
2.1 问题转化
这道题的关键在于理解"正方形空洞"的概念。我们需要找到一个正方形区域,其内部不包含任何线段(无论是横线还是竖线)。这意味着:
- 正方形区域的四条边必须由网格线组成
- 正方形内部不能有任何穿过它的横线或竖线
2.2 核心观察
通过分析题目和示例,我们可以得出以下重要观察:
- 最大正方形空洞的边长由水平和垂直方向上的最大连续可移除线段区间决定
- 正方形边长等于水平和垂直方向上最大连续可移除区间的最小值
- 面积就是这个边长的平方
例如,如果水平方向最大可以移除连续3条线,垂直方向最大可以移除连续5条线,那么最大正方形空洞的边长就是min(3,5)=3,面积为9。
2.3 算法选择
基于上述观察,解题步骤可以分解为:
- 分别计算水平方向和垂直方向上的最大连续可移除线段区间
- 取这两个方向上的最小连续值作为正方形边长
- 返回边长的平方
计算最大连续可移除线段区间的算法可以采用排序+单次遍历的方式:
- 首先对hBars和vBars进行排序
- 然后遍历排序后的数组,寻找最长的连续递增子序列(间隔为1)
- 最大连续区间长度就是这个最长连续递增子序列的长度加1(因为移除k条连续线会形成k+1个连续空格)
3. 详细解法实现
3.1 预处理:排序
由于题目没有保证hBars和vBars是有序的,我们需要先对它们进行排序:
cpp复制sort(hBars.begin(), hBars.end());
sort(vBars.begin(), vBars.end());
排序的时间复杂度是O(h log h + v log v),其中h和v分别是hBars和vBars的长度。
3.2 计算最大连续可移除区间
定义一个辅助函数getMaxDiff来计算最大连续可移除区间:
cpp复制int getMaxDiff(vector<int>& v) {
if (v.empty()) return 1; // 如果没有可移除的线,最大连续区间是1(原始网格)
int last = v[0], cnt = 1, ans = 1;
for (int i = 1; i < v.size(); i++) {
if (v[i] == last + 1) {
cnt++;
last++;
} else {
ans = max(ans, cnt);
cnt = 1;
last = v[i];
}
}
ans = max(ans, cnt);
return ans + 1; // 连续移除cnt条线,形成cnt+1个连续空格
}
这个函数的工作原理:
- 初始化last为第一个元素,cnt和ans为1
- 遍历数组,如果当前元素等于last+1,说明是连续的,增加cnt
- 否则,更新ans并重置cnt
- 最后返回最大cnt+1(因为连续移除k条线会形成k+1个连续空格)
3.3 主函数实现
主函数只需要调用上述辅助函数并取最小值:
cpp复制int maximizeSquareHoleArea(int n, int m, vector<int>& hBars, vector<int>& vBars) {
sort(hBars.begin(), hBars.end());
sort(vBars.begin(), vBars.end());
int hMax = getMaxDiff(hBars);
int vMax = getMaxDiff(vBars);
int side = min(hMax, vMax);
return side * side;
}
4. 复杂度分析
- 时间复杂度:O(h log h + v log v),其中h和v分别是hBars和vBars的长度。主要来自排序操作。
- 空间复杂度:O(log h + log v),这是排序所需的栈空间(快速排序的平均情况)。
5. 边界情况与测试用例
5.1 边界情况考虑
- 空输入:hBars或vBars为空时,最大连续区间为1
- 单元素输入:最大连续区间为2
- 完全连续输入:如[2,3,4,5],最大连续区间为输入长度+1
- 完全不连续输入:如[2,4,6],最大连续区间为2
- 混合连续输入:如[2,3,5,6,8,9,10],最大连续区间为3(对应8,9,10)
5.2 测试用例验证
cpp复制void test() {
Solution sol;
// 示例1
vector<int> h1 = {2,3}, v1 = {2};
assert(sol.maximizeSquareHoleArea(2,1,h1,v1) == 4);
// 示例2
vector<int> h2 = {2}, v2 = {2};
assert(sol.maximizeSquareHoleArea(1,1,h2,v2) == 4);
// 示例3
vector<int> h3 = {2,3}, v3 = {2,3,4};
assert(sol.maximizeSquareHoleArea(2,3,h3,v3) == 9);
// 空输入测试
vector<int> h4 = {}, v4 = {};
assert(sol.maximizeSquareHoleArea(2,3,h4,v4) == 1);
// 完全不连续测试
vector<int> h5 = {2,4,6}, v5 = {3,6,9};
assert(sol.maximizeSquareHoleArea(10,10,h5,v5) == 4);
cout << "所有测试用例通过!" << endl;
}
6. 优化与扩展思考
6.1 算法优化
当前的算法已经相当高效,但可以做一些小的优化:
- 提前终止:如果在遍历过程中已经找到可能的最大值(如当前连续长度已经等于数组长度),可以提前终止
- 并行计算:hBars和vBars的处理可以并行进行
6.2 问题扩展
这个问题可以有几种有趣的变体:
- 矩形空洞而非正方形:求最大矩形空洞面积
- 加权移除:不同线段有不同的移除成本,在有限总成本下求最大空洞
- 动态网格:支持动态添加和移除线段,需要维护最大空洞信息
7. 实际应用场景
这类网格操作问题在实际中有多种应用:
- 城市规划:确定建筑物之间的最大正方形空地
- 图像处理:寻找二值图像中的最大空白正方形区域
- 游戏设计:在网格类游戏中计算最大可放置区域
- VLSI设计:在芯片布局中寻找最大连续空白区域
8. 常见错误与调试技巧
在实现这个算法时,容易犯的错误包括:
- 忘记排序:导致无法正确计算连续区间
- 边界处理不当:特别是当输入数组为空时
- 连续区间计算错误:容易忘记最后还需要一次max操作
- 边长计算错误:应该取min(hMax, vMax)而非max
调试技巧:
- 打印中间结果:在getMaxDiff函数中打印last、cnt和ans的值
- 小规模测试:先用小例子手动计算预期结果
- 边界测试:专门测试空输入、单元素输入等情况
9. 代码实现细节
完整的C++实现如下:
cpp复制#include <vector>
#include <algorithm>
using namespace std;
class Solution {
private:
int getMaxDiff(vector<int>& v) {
if (v.empty()) return 1;
int last = v[0], cnt = 1, ans = 1;
for (int i = 1; i < v.size(); i++) {
if (v[i] == last + 1) {
cnt++;
last++;
} else {
ans = max(ans, cnt);
cnt = 1;
last = v[i];
}
}
ans = max(ans, cnt);
return ans + 1;
}
public:
int maximizeSquareHoleArea(int n, int m, vector<int>& hBars, vector<int>& vBars) {
sort(hBars.begin(), hBars.end());
sort(vBars.begin(), vBars.end());
int hMax = getMaxDiff(hBars);
int vMax = getMaxDiff(vBars);
int side = min(hMax, vMax);
return side * side;
}
};
10. 总结与个人体会
这道题目看似复杂,但通过仔细分析可以发现其核心在于寻找最大连续区间。关键在于:
- 将二维问题分解为两个一维问题
- 理解连续移除线段与形成连续空格之间的关系
- 通过排序简化连续区间的查找
在实际编程竞赛中,这类题目考验的是问题转化能力。我的经验是:
- 先画图理解示例,确保完全明白题目要求
- 寻找问题中的规律和可分解的部分
- 考虑边界情况和极端输入
- 编写清晰、模块化的代码,便于调试和验证
对于类似的网格操作问题,这种"降维"思想(将二维问题转化为一维问题)是非常有用的解题技巧。