LeetCode1272:重新排列后的最大子矩阵

Leetcode1272

该题与LeetCode84:柱状图中的最大矩形LeeCode85:最大矩形息息相关。后面我会将这三道题整理到一个系列中。

核心思想就是计算这个矩形的每一层向上 \(1\) 延伸的高度,将每一层作为一个柱状图,问题转化为计算柱状图中的最大矩形。非常类似于LeeCode85:最大矩形的做法。不过不同的是,在上面两题中,高度不能交换位置,所以计算底边长度需要通过单调栈快速计算。本题中可以直接使用排序,排序后每个柱形能伸展的宽度得到最大化,对应的面积也达到最大。

枚举底边+排序

class Solution {
public:
    // 相比于不能重新排列的反而简单,直接排序每个可能延申的最大宽度即为从左到当前位置的长度(greater排序)
    // 如果不能排序需要使用单调栈处理
    int compute_max(vector<int> row){
        int ans=0; 
        int n=row.size();
        sort(row.begin(),row.end(),greater<int>());

        for(int i=0;i<n;++i){
            ans=max(ans,row[i]*(i+1));
        }

        return ans;
    }

    int largestSubmatrix(vector<vector<int>>& matrix) {
        int m=matrix.size();
        int n=matrix[0].size();
        vector<vector<int>> height(m+1,vector<int>(n,0));
        
        // 计算向上延申的柱子高度
        for(int i=1;i<m+1;++i){
            for(int j=0;j<n;++j){
                if (matrix[i-1][j]){
                    height[i][j]=height[i-1][j]+matrix[i-1][j];
                }
            }
        }
        
        int ans=0;
        for(auto& row:height){
            ans=max(ans,compute_max(row));
        }

        return ans;
    }
};

优化

灵神题解中提出了一种优化策略可以优化掉 \(logn\) 的排序。核心思想是,层之间高度转移时,当前不为 \(0\)matrix网格对应的height均增加相同的 \(1\),增加后排序结果不变;而当前位置为 \(0\) 的网格height会变为 \(0\) 移动至最前方(最小),无需排序。最后两部分拼接到一起,就能够维护保持整体的顺序,无需使用 \(logn\) 的排序算法。又因为起始层非 \(0\)\(1\),初始化时也是把 \(0\) 放在前面,\(1\) 放在后面,初始方法和每层之间的转移方法统一,无需特别处理,非常优雅。不过因为移动会导致无法确定当前位置对应矩形哪个位置,需要额外创建一个索引来记录。

优化1

因为在循环中申请内存,实际上很慢。

class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int m=matrix.size();
        int n=matrix[0].size();
        vector<int> height(n,0);
        vector<int> idx(n);
        ranges::iota(idx,0);
        
        int ans=0;
        for (int i=0;i<m;++i){
            // 这里可以优化,在循环中申请内存,很慢
            vector<int> zero_idx;
            vector<int> one_idx;
            for(int j=0;j<n;++j){
                // 记录0位置
                if (matrix[i][idx[j]]==0){
                    zero_idx.push_back(idx[j]);
                    height[idx[j]]=0;
                }else{
                    // 记录1位置
                    one_idx.push_back(idx[j]);
                    height[idx[j]]+=1;
                }
            }

            // 记录起点
            int start=zero_idx.size();
            // 拼接,0在前,1在后。
            zero_idx.insert(zero_idx.end(),one_idx.begin(),one_idx.end());
            // 更新排序idx
            idx=zero_idx;

            // 从起点开始,零值部分不用计算
            for (int i=start;i<n;++i){
                ans=max(ans,height[idx[i]]*(n-i));
            }
        }

        return ans;
    }
};

优化2

在优化1的基础上在循环外分配内存,不过循环内就需要原地操作,理解起来麻烦了。

class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int m=matrix.size();
        int n=matrix[0].size();
        vector<int> height(n,0);
        vector<int> idx(n);

        // 循环外申请内存
        vector<int> none_zero(n);
        ranges::iota(idx,0);
        
        int ans=0;
        for (int i=0;i<m;++i){
            int p=0,q=0;
            for(int j=0;j<n;++j){
                if (matrix[i][idx[j]]==0){
                    // idx原地更新零的部分
                    idx[p++]=idx[j];
                    height[idx[j]]=0;
                }else{
                    // none_zero存储1的部分
                    none_zero[q++]=idx[j];
                    height[idx[j]]+=1;
                }
            }

            // 从起点开始,零值部分不用计算
            for (int i=p;i<n;++i){
                // 将1的部分更新到idx中
                idx[i]=none_zero[i-p];
                ans=max(ans,height[idx[i]]*(n-i));
            }
        }

        return ans;
    }
};
posted @ 2026-03-17 17:05  冰雪聪明琪露诺  阅读(7)  评论(0)    收藏  举报