1727 - 重新排列后的最大子矩阵 - 枚举 - 贪心

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

@[toc]

题目描述

[1727] 重新排列后的最大子矩阵

给你一个二进制矩阵 matrix ,它的大小为 m x n ,你可以将 matrix 中的 列 按任意顺序重新排列。

请你返回最优方案下将 matrix 重新排列后,全是 1 的子矩阵面积。

示例 1:

输入:matrix = [[0,0,1],[1,1,1],[1,0,1]]
输出:4
解释:你可以按照上图方式重新排列矩阵的每一列。
最大的全 1 子矩阵是上图中加粗的部分,面积为 4 。
示例 2:

输入:matrix = [[1,0,1,0,1]]
输出:3
解释:你可以按照上图方式重新排列矩阵的每一列。
最大的全 1 子矩阵是上图中加粗的部分,面积为 3 。
示例 3:

输入:matrix = [[1,1,0],[1,0,1]]
输出:2
解释:由于你只能整列整列重新排布,所以没有比面积为 2 更大的全 1 子矩形。
示例 4:

输入:matrix = [[0,0],[0,0]]
输出:0
解释:由于矩阵中没有 1 ,没有任何全 1 的子矩阵,所以面积为 0 。

提示:

m == matrix.length
n == matrix[i].length
1 <= m * n <= 10^5
matrix[i][j] 要么是 0 ,要么是 1 。

Related Topics
  • 枚举
  • 贪心
  • 题目剖析&信息挖掘

    此题主要用枚举思想,将复杂问题简化,再利用贪心思想求解。

    解题思路

    方法一 枚举+贪心

    分析

    题目说每一列可以动,但是行不可以动。由于题目说是要取其中的一个子矩阵,矩阵的上下边界必然是某一行。

    我们可以尝试枚举某一行row[i]为矩阵的底,求解出row[i]为底的最解best[i]。

    答案就是max(best[x]) x = [0, len(matrix)-1]。

    来看一下row[i]为底时,要面对的问题。

    在上图中,以row[2]为底,那么红色中的1才是有效的区域,其他列不管怎么移,也不能满足以row[2]为底构造出一个矩阵。

    那么现在的问题就转化为在同一平面上给出一些宽度为1,高度不等的长方形,问如何移动得到一个最大的矩形。

    设数组h,长度为len(row[i]), h[j]表示从row[i][j]开始向上连续为1的个数。

    上图中h = [2,1,1,2,0]。枚举高度为x = [1, max(h[i])], 查看高度>=x 的数量 c, 取所有x*c 最大值即为答案。查找数量c可以对h进行排序解决。

    思路

    • 初始h = [0,0,…,0]
    • 对于一行row[i], 如果row[i\][j]=0, 则h[j]=0,否则h[j]++。
    • 计算出当前行为底的最优解。
    • 求出所有行为底的最优解,综合取最大值。
    • 复杂度:遍历一行并求解复杂度为O(mlog(m)), 总体O(n*mlog(m))
    func largestSubmatrix(matrix [][]int) int {
        lenArr := make([]int, len(matrix[0])) // 存储当前高度
        best :=0 // 存储最优解
        for _, row := range matrix {
            // 遍历当前行并计算高度
    
          // 计算当前行为底的最优解
        }
    
        return best
    }
    
    
    func max(a, b int) int {
    	if a>b{return a}
    	return b
    }
    
    

    注意

    • 求解时原来的h数组不能换顺序,要自己重新复制一份。

    知识点

    • 枚举
    • 贪心

    复杂度

    • 时间复杂度:O(n*mlog(m))
    • 空间复杂度:O(n*m)

    代码实现

    func largestSubmatrix(matrix [][]int) int {
        lenArr := make([]int, len(matrix[0])) // 存储当前高度
        best :=0 // 存储最优解
        for _, row := range matrix {
          // 遍历当前行并计算高度
            for i, v := range row {
                if v ==0 {
                    lenArr[i]=0
                } else {lenArr[i]++}
            }
    
    
            // 计算当前行为底的最优解
            tempArr := make([]int, len(lenArr)) // 复制备份,不要破坏原来的数组
            copy(tempArr,lenArr)
    
            sort.Slice(tempArr, func(a, b int ) bool {return tempArr[a]>tempArr[b]})
    
            for i, v := range tempArr {
                best = max(best, (i+1)*v)
            }
        }
    
        return best
    }
    
    
    func max(a, b int) int {
    	if a>b{return a}
    	return b
    }
    

    相关题目

    https://leetcode-cn.com/problems/maximal-rectangle/

    https://leetcode-cn.com/problems/maximal-square/

    https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

    在这里插入图片描述

    比较麻烦的向下方法

    
    
    func largestSubmatrix(matrix [][]int) int {
    	/*
    		1.对每一行,计算当前位置向下的连续列长,计算最大的子矩阵
    		2. 列长排序后,就容易以当前列长为边的最大子矩阵
    
    	*/
    
    	rows := len(matrix)
    	if rows == 0 {
    		return 0
    	}
    
    	res := 0
    	cols := len(matrix[0])
    
    	preHis := make([]int, cols) //记录上一行,每个点往下的连续列长
    
    	for rowID := 0; rowID < rows; rowID++ {
    		curHis := make([]int, 0)
    		for colID := 0; colID < cols; colID++ {
    			//计算 matrix[rowID][colID]开始向下的连续1的长度
    			if preHis[colID] > 1 { //如果上面已经计算好 连续长度了,就减去一就可以了
    				preHis[colID]--
    				curHis = append(curHis, preHis[colID])
    				continue
    			} else if matrix[rowID][colID] == 0 {
    				preHis[colID] = 0 //注意这里要清0
    				continue
    
    			}
    
    			curLen := 1
    			//往下找1
    			for j := rowID + 1; j < rows; j++ {
    				if matrix[j][colID] == 0 {
    					break
    				}
    				curLen++
    			}
    
    			preHis[colID] = curLen
    			curHis = append(curHis, preHis[colID])
    
    		}
    
    		//对curHis排序,然后计算以当前列长为边的最大子矩阵
    		sort.Ints(curHis)
    		// tmpMax := 0
    		for i, h := range curHis {
    			res = max(res, h*(len(curHis)-i))
    		}
    
    	}
    
    	return res
    }
    
    func max(i, j int) int {
    	if i > j {
    		return i
    	}
    
    	return j
    }
    

    84. 柱状图中最大的矩形

    难度困难1147收藏分享切换为英文接收动态反馈

    给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

    求在该柱状图中,能够勾勒出来的矩形的最大面积。

    package leetcode
    
    /*
    
     利用单调栈记录,以当前柱子为最低值的,左右范围区间
    
    */
    
    func largestRectangleArea(heights []int) int {
    
        leftStack := []int{}
    
        rightStack := []int{}
    
        leftPos := make([]int, len(heights))
    
        rightPos := make([]int, len(heights))
    
        for i, h := range heights {
    
            for len(leftStack) > 0 {
    
                tail := leftStack[len(leftStack)-1]
    
                if heights[tail] >= h {
    
                    leftStack = leftStack[:len(leftStack)-1]
    
                } else {
    
                    break
    
                }
    
            }
    
            if len(leftStack) == 0 {
    
                leftPos[i] = -1
    
            } else {
    
                leftPos[i] = leftStack[len(leftStack)-1]
    
            }
    
            leftStack = append(leftStack, i)
    
        }
    
        for j := len(heights) - 1; j >= 0; j-- {
    
            h := heights[j]
    
            for len(rightStack) > 0 {
    
                tail := rightStack[len(rightStack)-1]
    
                if heights[tail] >= h {
    
                    rightStack = rightStack[:len(rightStack)-1]
    
                } else {
    
                    break
    
                }
    
            }
    
            if len(rightStack) == 0 {
    
                rightPos[j] = len(heights)
    
            } else {
    
                rightPos[j] = rightStack[len(rightStack)-1]
    
            }
    
            rightStack = append(rightStack, j)
    
        }
    
        res := 0
    
        for i, h := range heights {
    
            res = max(res, (rightPos[i]-leftPos[i]-1)*h)
    
        }
    
        return res
    
    }
    
    func max(i, j int) int {
    
        if i > j {
    
            return i
    
        }
    
        return j
    
    }
    
    /*
     farthestArr[i] 代表从第i个柱子往左连续比自身高的最远柱子
     利用单调栈
    */
    func getFarthestArr(heights []int) []int {
     stack := make([]int, len(heights))
     top := 0
     farthestArr := make([]int, len(heights))
     for i, h := range heights {
      for ;top>0 && heights[stack[top-1]]>=h;top-- {} // 每个数字最多进栈出栈一次。总复杂度O(n)
      if top == 0{farthestArr[i]=0 // 可以到最左端
      } else {
       farthestArr[i] = stack[top-1]+1
      }
    
      stack[top] = i
      top++
     }
     return farthestArr
    }
    
    func reverse(arr []int) {
     for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
      arr[i] = arr[i] ^ arr[j]
      arr[j] = arr[i] ^ arr[j]
      arr[i] = arr[i] ^ arr[j]
     }
    }
    
    func max(a, b int) int {
     if a > b {
      return a
     }
     return b
    }
    
    func largestRectangleArea(heights []int) int {
     if len(heights) == 0 {
      return 0
     }
     leftPos := getFarthestArr(heights)
     // 利用反转求 rightPos
     reverse(heights)
     rightPos := getFarthestArr(heights) // 存储的是 len(height) - rightPos 计算时作转化
     reverse(heights)
     reverse(rightPos)
    
     best := 0
     for i, h := range heights {
      best = max(best, h*(len(heights)-rightPos[i]-leftPos[i]))
     }
     return best
    }
    
    /*
    [2,1,5,6,2,3]
    [1,2,3,4,5,6,7,8]
    [1]
    [1000]
    [1,6,20,233,222,33,1,22,1,222,222,222,333,44,555,66,1,2,3,4,999,88,2,77,77,33,22]
    [4,2,0,3,2,4,3,4]
    */
    

    「 木头: 「公众号彬彬魔坊:
    就讨论相联2根柱子的关系就可以了,和之前那个矩阵的h计算类似。」
    这里我思考得不对。


    求代码[让我看看] 」


    基本思路还是和你的差不多。