第三周LeetCode记录

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

总结

用栈结构存储了上一次的信息。

  1. 什么时候会弹栈和停止弹栈:当遍历到的高度小于最后一个元素时,说明矩形达到边界,弹栈依次计算。直至元素小于遍历到的高度,说明达到左边届。
  2. 弹出时候栈为空是什么状态:说明遍历的高度最矮,是最小的高度。
  3. 弹出时遇到为0,栈还可以为空吗:如果列表中包含0,非0则不可能把0弹出去。如果0弹0,高度是0计算出来也是0,没有影响。
  4. 遍历完成后,栈内元素的宽度怎么算:当遍历完成后,栈中至少有一个元素,即最小,左右边界都达到,说明宽度为数组长度。如果pop得到高度不为空,右边界还是取到。总之,栈内元素都可以达到右边界。
  5. 哨兵模式:新增一个首元素为0,这样就不会空栈,尾元素为0,就会弹栈计算面积最大值。
  • 单调栈

    栈内元素维持了单调性的场景

    1. 单调递增栈可以找到左边第一个比当前出栈元素小的元素
    2. 单调递减栈可以找到左边第一个比当前出栈元素大的元素

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,两点之间的关系用图中的边的关系对应。可以用相邻的点来连接。溯源来深度遍历所有的节点。

posted @ 2020-09-27 21:45  Jimmyhe  阅读(111)  评论(0编辑  收藏  举报