专栏 | 九章算法
网址 | http://www.jiuzhang.com
题目描述
给一个二维数组构成的网格,其中的每一个格点非0即1。找出由4个不同格点上的1作为4个角,撑起的矩形个数。
注意:
1. 该矩形边与横轴或纵轴平行。
2. 只需要4个角有1即可。
3. 行数和列数范围为[1, 200]
4. 一块网格中1的总数不超过6000。
样例:
输入:
[[1, 0, 0, 1, 0],
[0, 0, 1, 0, 1],
[0, 0, 0, 1, 0],
[1, 0, 1, 0, 1]]
复制代码
输出:
1
复制代码
解释:
由于只有[1][2], [1][4], [3][2], [3][4],这四个点能组成矩形。故输出1。
复制代码
输入:
[[1, 1, 1],
[1, 1, 1],
[1, 1, 1]]
复制代码
输出:
9
复制代码
解释:
有4个2x2的矩形,4个2x3的矩形,1个3x3的矩形。
复制代码
输入:
[[1, 1, 1, 1]]
复制代码
输出:
0
复制代码
解释:
注意,四个角任意两个不能共点
复制代码
解题思路分析
最直观的想法是穷举所有的矩形,看其是否满足要求。由于R行C列的网格,矩形一共有R * (R - 1) * C * (C - 1)个,所以穷举的时间复杂度是O((R * C) ^ 2),相当费时。有没有什么好的方法呢?
如果换一种思路考虑:每加入新的一行,会新加多少个矩形?这种想法有一些像动态规划,复杂度会比穷举法要低不少。由此就引出以下两种方法:
方法一:
对于新行的每对1(记为cur_row[i]和cur_row[j]),新加矩形个数,是之前行row[i]= row[j] = 1出现的次数!所以逐行遍历网格,并维护一个统计表count[i,j](可用map实现,python中亦可用Counter或defaultdict实现),count记录了之前行的每一对row[i] = row[j] = 1的次数。对于新行的每对1,例如cur_row[i]和cur_row[j],最终结果加上count[i, j],然后count[i, j]自增。
方法一复杂度分析:
时间复杂度: O(R ∗ C^2),其中R是行数,C是列数。由于遍历每行当中都要寻找每对1,故每行遍历复杂度为C ^ 2。
空间复杂度:O(C ^ 2)。
方法二(难度较大):
在方法一中每一行都要O(C ^ 2)时间去遍历,这个过程十分麻烦。很容易想到,可以先将每一行的所有1的位置记录到列表当中,一共有R个这样的列表。然后行遍历过程中,直接对列表里的每一对元素进行统计表count的查询和自增即可。这是一种很好的办法,但是如果这一行1很多,也即这一行很密集,那么这个列表会很长,遍历每一对元素复杂度仍然会逼近O(C ^ 2)。
针对这种密集的行(记该行为r),一种改进措施是不再遍历其列表中每一对元素,而是直接寻找表中每一行(如行a)有多少个1,它们在r行对应列上也是1。假设有f个,那么行a与行f所组成的矩形就有(f - 1) * f / 2个。我们可以用把行r的列表转换为集合Set,然后只需线性遍历每一行,计算出f及其产生的矩形数。要注意如果如果行r和行a都是密集行,那么行a只有在行r的下方有效。否则会重复计算,也即遍历到行r计算了一次,遍历到行a又计算了一次。
那么到底一行超过多少个点算密集行,这里取的是N ^ 0.5,也即根号N,其中N为网格中1的个数。这样能保证最多只有N ^ 0.5个密集行,每一个密集行的复杂度为O(N),故复杂度不超过N ^ 1.5。而对于稀疏行,因总点数不超过N,所以可证所有的稀疏行总共复杂度不超过N ^ 1.5(证明复杂,需用数学推导,这里略去)。
方法二复杂度分析:
时间复杂度:由以上分析可得O(N ^ 1.5)。
空间复杂度:O(N + R + C ^ 2)。N是由于记录1的位置的列表,R是由于由R个列表,C ^ 2是由于统计表count。
参考程序
面试官角度分析
本题是一道稍难的统计类题目,很考验面试者的观察能力,改变统计的思路从而减小时间复杂度。同时这道题考察了对于Set、Map等数据结构的使用。如果能想到逐行添加的思路,进而给出方法一,可以给出Hire;如果能在方法一的基础上进行优化,对于密集边进行单独处理并能分析出其复杂度,可以给出Strong Hire。
lintcode相关问题
精英程序员交流社区,定期发布面试题、面试技巧、求职信息等