9.20 13.最大矩形
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:
输入:
[
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
输出: 6
思路
两次遍历,第一次最大元素个数,第二次逐行遍历
我的解
时间复杂度过高
最优解一
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
if not matrix: return 0
m = len(matrix)
n = len(matrix[0])
left = [0] * n # initialize left as the leftmost boundary possible
right = [n] * n # initialize right as the rightmost boundary possible
height = [0] * n
maxarea = 0
for i in range(m):
cur_left, cur_right = 0, n
# update height
for j in range(n):
if matrix[i][j] == '1': height[j] += 1
else: height[j] = 0
# update left
for j in range(n):
if matrix[i][j] == '1': left[j] = max(left[j], cur_left)
else:
left[j] = 0
cur_left = j + 1
# update right
for j in range(n-1, -1, -1):
if matrix[i][j] == '1': right[j] = min(right[j], cur_right)
else:
right[j] = n
cur_right = j
# update the area
for j in range(n):
maxarea = max(maxarea, height[j] * (right[j] - left[j]))
return maxarea
问题
Q:为什么所有的right都要从5开始。所有的left从0开始
A: 如果是[1,0,0],left[0,0,0],right[2,3,3],如果右边遇到了0,则说明碰到了有边界,同上方的右边界相比,谁小取谁。如果左边遇到了0,和上方的左边界相比,谁大取谁。因为上方的边界包含了上部所有的,所以边界一定是矩形的边界。每次比较的时候是拿上一次的同列的结果,去和当前列的坐标相比,所以右边取最大(可以用min取出右边最小坐标),左边取最小(用max取出左边最大坐标)
总结
是对点的遍历,每个点取最大的矩形计算,每个点可以引用上一个点的结果。
9.22 14. 柱状图中最大矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
输入: [2,1,5,6,2,3]
输出: 10
思路
暴力破解,从左到右,自上而下,分别求出每个高度可以得到的最大矩形。
我的解
class Solution:
@classmethod
def largestRectangleArea(self, heights:list) -> int:
list_len = len(heights)
if list_len==1:
return heights[0]
area = 0
# 遍历列表起始坐标
for index in range(list_len):
max_num = heights[index]
# 高度
for i in range(1,max_num+1):
# 当前坐标
for con in range(index,list_len):
# 比较高度
if i > heights[con]:
area = max((con-index)*i,area)
break
if con == list_len -1:
area = max((list_len - index) * i,area)
return area
if __name__ == "__main__":
res = Solution.largestRectangleArea([0,0,0,0,0,0,0,0,2147483647])
print(res)
最优解
class Solution:
@classmethod
def largestRectangleArea(self, heights: list) -> int:
if len(heights) == 0: return 0
if len(heights) == 1: return heights[-1]
n = len(heights)
stack = list()
area = 0
for i in range(n):
# 当栈里有元素,且大于添加的数时
while stack and heights[stack[-1]] > heights[i]:
# 记录高度
a = stack.pop()
height = heights[a]
# 如果最后一个元素高度和之前一样继续弹出
while stack and heights[stack[-1]] == height:
stack.pop()
# 栈顶元素确定的宽度,如果栈为空,则说明添加的元素最矮,可以延伸到左边届
if len(stack)==0:
width = i
# 如果栈不为空
else:
# i-1是当前高度对应的下标
width = i - stack[-1] - 1
area = max(area,width*height)
stack.append(i)
# 当遍历完成后,栈里还有元素,添加的元素递增或相等
while stack:
# 记录高度
a = stack.pop()
height = heights[a]
# 如果最后一个元素高度和之前一样继续弹出
while stack and heights[stack[-1]] == height:
stack.pop()
# 重要:无论栈是否为空,当前元素一定可以扩散到最右边
if len(stack) == 0:
width = n
# 如果栈不为空
else:
width = n - stack[-1] - 1
area = max(area, width * height)
return area
优化:哨兵模式
def largestRectangleArea(self, heights: list) -> int:
import copy
if len(heights) == 0: return 0
if len(heights) == 1: return heights[-1]
stack = list()
stack.append(0)
new_heights = copy.deepcopy(heights)
new_heights.insert(0,0)
new_heights.append(0)
heights = new_heights
n = len(heights)
area = 0
for i in range(1,n):
# 当栈里有元素,且大于添加的数时
while heights[stack[-1]] > heights[i]:
# 记录高度
a = stack.pop()
height = heights[a]
# 如果最后一个元素高度和之前一样继续弹出
while heights[stack[-1]] == height:
stack.pop()
width = i - stack[-1] - 1
area = max(area,width*height)
stack.append(i)
return area
总结
用栈结构存储了上一次的信息。
- 什么时候会弹栈和停止弹栈:当遍历到的高度小于最后一个元素时,说明矩形达到边界,弹栈依次计算。直至元素小于遍历到的高度,说明达到左边届。
- 弹出时候栈为空是什么状态:说明遍历的高度最矮,是最小的高度。
- 弹出时遇到为0,栈还可以为空吗:如果列表中包含0,非0则不可能把0弹出去。如果0弹0,高度是0计算出来也是0,没有影响。
- 遍历完成后,栈内元素的宽度怎么算:当遍历完成后,栈中至少有一个元素,即最小,左右边界都达到,说明宽度为数组长度。如果pop得到高度不为空,右边界还是取到。总之,栈内元素都可以达到右边界。
- 哨兵模式:新增一个首元素为0,这样就不会空栈,尾元素为0,就会弹栈计算面积最大值。
-
单调栈
栈内元素维持了单调性的场景
- 单调递增栈可以找到左边第一个比当前出栈元素小的元素
- 单调递减栈可以找到左边第一个比当前出栈元素大的元素
9.24 15. 除法求值
给出方程式 A / B = k, 其中 A 和 B 均为用字符串表示的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。
示例 :
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]
思路
对于给定的两个列表,求交集,把变量统一。
我的解
解法过于笨重,要分情况讨论交集为0,1,2三种情况。要根据交集判断是除数还是被除数,如果是只有一个交集,判断是否为(a,a),(a,b)还是(a,b),(a,c),判断很繁琐。
最优解
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
# 构造图,equations的第一项除以第二项等于value里的对应值,第二项除以第一项等于其倒数
graph = {}
for (x, y), v in zip(equations, values):
if x in graph:
graph[x][y] = v
else:
graph[x] = {y: v}
if y in graph:
graph[y][x] = 1/v
else:
graph[y] = {x: 1/v}
# dfs找寻从s到t的路径并返回结果叠乘后的边权重即结果
def dfs(s, t) -> int:
if s not in graph:
return -1
if t == s:
return 1
for node in graph[s].keys():
if node == t:
return graph[s][node]
elif node not in visited:
visited.add(node) # 添加到已访问避免重复遍历
v = dfs(node, t)
if v != -1:
return graph[s][node]*v
return -1
# 逐个计算query的值
res = []
for qs, qt in queries:
visited = set()
res.append(dfs(qs, qt))
return res
总结:
图解法和dfs,两点之间的关系用图中的边的关系对应。可以用相邻的点来连接。溯源来深度遍历所有的节点。