普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月22日LeetCode 每日一题题解

每日一题-判断矩阵经轮转后是否一致🟢

2026年3月22日 00:00

给你两个大小为 n x n 的二进制矩阵 mattarget 。现 以 90 度顺时针轮转 矩阵 mat 中的元素 若干次 ,如果能够使 mat 与 target 一致,返回 true ;否则,返回 false

 

示例 1:

输入:mat = [[0,1],[1,0]], target = [[1,0],[0,1]]
输出:true
解释:顺时针轮转 90 度一次可以使 mat 和 target 一致。

示例 2:

输入:mat = [[0,1],[1,1]], target = [[1,0],[0,1]]
输出:false
解释:无法通过轮转矩阵中的元素使 equal 与 target 一致。

示例 3:

输入:mat = [[0,0,0],[0,1,0],[1,1,1]], target = [[1,1,1],[0,1,0],[0,0,0]]
输出:true
解释:顺时针轮转 90 度两次可以使 mat 和 target 一致。

 

提示:

  • n == mat.length == target.length
  • n == mat[i].length == target[i].length
  • 1 <= n <= 10
  • mat[i][j]target[i][j] 不是 0 就是 1

枚举四种情况进行比较即可

作者 qyaaaa
2021年6月6日 12:52
public boolean findRotation(int[][] mat, int[][] target) {
        int n = mat.length;
        boolean b1 = true,b2 = true,b3 = true,b4 = true;
        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                //旋转90度
                if(mat[n - j - 1][i] != target[i][j]){
                    b1 = false;
                }
                //旋转180度
                if (mat[n - i - 1][n - j - 1] != target[i][j]){
                    b2 = false;
                }
                //旋转270度
                if (mat[j][n- i- 1] != target[i][j]){
                    b3 = false;
                }
                //旋转360度
                if (mat[i][j] != target[i][j]){
                    b4 = false;
                }
            }
        }
        return b1 || b2 || b3 || b4;
    }

[Python] 判断顺时针转0度,90度,180度和270度是否一样

作者 himymBen
2021年6月6日 12:50

解题思路

该用户太懒了见代码

代码

###python3

class Solution:
    def findRotation(self, mat: List[List[int]], target: List[List[int]]) -> bool:
        def rotate(matrix):
            m,n = len(matrix),len(matrix[0])
            new = [[0] * m for _ in range(n)]
            for i in range(m):
                for j in range(n):
                    new[j][m-1-i] = matrix[i][j]
            return new
        
        if mat == target:
            return True
        
        for i in range(3):
            mat = rotate(mat)
            if mat == target:
                return True
        return False

两种方法:先旋转再比较 / 直接比较(Python/Java/C++/Go)

作者 endlesscheng
2021年6月6日 12:34

方法一:先旋转再比较

枚举 $\textit{mat}$ 旋转 $0,1,2,3$ 次,判断旋转后的 $\textit{mat}$ 是否等于 $\textit{target}$。

旋转方阵有原地算法,见 48. 旋转图像我的题解

class Solution:
    # 48. 旋转图像
    def rotate(self, matrix: List[List[int]]) -> None:
        n = len(matrix)
        for i, row in enumerate(matrix):
            for j in range(i + 1, n):  # 遍历对角线上方元素,做转置
                row[j], matrix[j][i] = matrix[j][i], row[j]
            row.reverse()  # 行翻转

    def findRotation(self, mat: List[List[int]], target: List[List[int]]) -> bool:
        for _ in range(4):
            if mat == target:
                return True
            self.rotate(mat)
        return False
class Solution {
    public boolean findRotation(int[][] mat, int[][] target) {
        for (int i = 0; i < 4; i++) {
            if (Arrays.deepEquals(mat, target)) {
                return true;
            }
            rotate(mat);
        }
        return false;
    }

    // 48. 旋转图像
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n; i++) {
            int[] row = matrix[i];
            for (int j = i + 1; j < n; j++) { // 遍历对角线上方元素,做转置
                int tmp = row[j];
                row[j] = matrix[j][i];
                matrix[j][i] = tmp;
            }
            for (int j = 0; j < n / 2; j++) { // 遍历左半元素,做行翻转
                int tmp = row[j];
                row[j] = row[n - 1 - j];
                row[n - 1 - j] = tmp;
            }
        }
    }
}
class Solution {
    // 48. 旋转图像
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) { // 遍历对角线上方元素,做转置
                swap(matrix[i][j], matrix[j][i]);
            }
            ranges::reverse(matrix[i]); // 行翻转
        }
    }

public:
    bool findRotation(vector<vector<int>>& mat, vector<vector<int>>& target) {
        for (int i = 0; i < 4; i++) {
            if (mat == target) {
                return true;
            }
            rotate(mat);
        }
        return false;
    }
};
// 48. 旋转图像
func rotate(matrix [][]int) {
n := len(matrix)
for i, row := range matrix {
for j := i + 1; j < n; j++ { // 遍历对角线上方元素,做转置
row[j], matrix[j][i] = matrix[j][i], row[j]
}
slices.Reverse(row) // 行翻转
}
}

func findRotation(mat, target [][]int) bool {
for range 4 {
if slices.EqualFunc(mat, target, slices.Equal[[]int]) {
return true
}
rotate(mat)
}
return false
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n^2)$,其中 $n$ 是 $\textit{mat}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(1)$。

方法二:直接比较

顺时针旋转 $90^\circ$ 后,位于 $(i,j)$ 的元素去哪了?

根据 48 题 我的题解,结论如下:

$$
(i,j)\xrightarrow{旋转\ 90^\circ} (j,n-1-i) \xrightarrow{旋转\ 90^\circ} (n-1-i,n-1-j) \xrightarrow{旋转\ 90^\circ} (n-1-j,i)
$$

所以对于 $\textit{mat}[i][j]$,它需要比较四个位置上的值:

  • 旋转 $0$ 次:比较 $\textit{target}[i][j]$。
  • 旋转 $1$ 次:比较 $\textit{target}[j][n-1-i]$。
  • 旋转 $2$ 次:比较 $\textit{target}[n-1-i][n-1-j]$。
  • 旋转 $3$ 次:比较 $\textit{target}[n-1-j][i]$。

如果对于某个旋转次数,所有的比较都为真,那么返回 $\texttt{true}$。否则返回 $\texttt{false}$。

class Solution:
    def findRotation(self, mat: List[List[int]], target: List[List[int]]) -> bool:
        ok = (1 << 4) - 1  # ok = [True] * 4
        for i, row in enumerate(mat):
            for j, x in enumerate(row):
                if x != target[i][j]:
                    ok &= ~(1 << 0)  # ok[0] = False
                if x != target[j][-1 - i]:
                    ok &= ~(1 << 1)  # ok[1] = False
                if x != target[-1 - i][-1 - j]:
                    ok &= ~(1 << 2)  # ok[2] = False
                if x != target[-1 - j][i]:
                    ok &= ~(1 << 3)  # ok[3] = False
                if ok == 0:  # 所有的 ok[i] 都是 False
                    return False
        return True  # 至少有一个 ok[i] 是 True
class Solution {
    public boolean findRotation(int[][] mat, int[][] target) {
        int n = mat.length;
        int ok = (1 << 4) - 1; // boolean[] ok = {true, true, true, true};
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int x = mat[i][j];
                if (x != target[i][j]) {
                    ok &= ~(1 << 0); // ok[0] = false
                }
                if (x != target[j][n - 1 - i]) {
                    ok &= ~(1 << 1); // ok[1] = false
                }
                if (x != target[n - 1 - i][n - 1 - j]) {
                    ok &= ~(1 << 2); // ok[2] = false
                }
                if (x != target[n - 1 - j][i]) {
                    ok &= ~(1 << 3); // ok[3] = false
                }
                if (ok == 0) { // 所有的 ok[i] 都是 false
                    return false;
                }
            }
        }
        return true; // 至少有一个 ok[i] 是 true
    }
}
class Solution {
public:
    bool findRotation(vector<vector<int>>& mat, vector<vector<int>>& target) {
        int n = mat.size();
        int ok = (1 << 4) - 1; // bool ok[4] = {true, true, true, true}
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int x = mat[i][j];
                if (x != target[i][j]) {
                    ok &= ~(1 << 0); // ok[0] = false
                }
                if (x != target[j][n - 1 - i]) {
                    ok &= ~(1 << 1); // ok[1] = false
                }
                if (x != target[n - 1 - i][n - 1 - j]) {
                    ok &= ~(1 << 2); // ok[2] = false
                }
                if (x != target[n - 1 - j][i]) {
                    ok &= ~(1 << 3); // ok[3] = false
                }
                if (ok == 0) { // 所有的 ok[i] 都是 false
                    return false;
                }
            }
        }
        return true; // 至少有一个 ok[i] 是 true
    }
};
func findRotation(mat, target [][]int) bool {
n := len(mat)
ok := 1<<4 - 1 // ok := [4]bool{true, true, true, true}
for i, row := range mat {
for j, x := range row {
if x != target[i][j] {
ok &^= 1 << 0 // ok[0] = false
}
if x != target[j][n-1-i] {
ok &^= 1 << 1 // ok[1] = false
}
if x != target[n-1-i][n-1-j] {
ok &^= 1 << 2 // ok[2] = false
}
if x != target[n-1-j][i] {
ok &^= 1 << 3 // ok[3] = false
}
if ok == 0 { // 所有的 ok[i] 都是 false
return false
}
}
}
return true // 至少有一个 ok[i] 是 true
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n^2)$,其中 $n$ 是 $\textit{mat}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

昨天 — 2026年3月21日LeetCode 每日一题题解

每日一题-垂直翻转子矩阵🟢

2026年3月21日 00:00

给你一个 m x n 的整数矩阵 grid,以及三个整数 xyk

整数 xy 表示一个 正方形子矩阵 的左上角下标,整数 k 表示该正方形子矩阵的边长。

你的任务是垂直翻转子矩阵的行顺序。

返回更新后的矩阵。

 

示例 1:

输入: grid = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], x = 1, y = 0, k = 3

输出: [[1,2,3,4],[13,14,15,8],[9,10,11,12],[5,6,7,16]]

解释:

上图展示了矩阵在变换前后的样子。

示例 2:

输入: grid = [[3,4,2,3],[2,3,4,2]], x = 0, y = 2, k = 2

输出: [[3,4,4,2],[2,3,2,3]]

解释:

上图展示了矩阵在变换前后的样子。

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • 1 <= grid[i][j] <= 100
  • 0 <= x < m
  • 0 <= y < n
  • 1 <= k <= min(m - x, n - y)

3643. 垂直翻转子矩阵

作者 navi-hex
2025年9月24日 20:39

Problem: 3643. 垂直翻转子矩阵

[TOC]

思路

模拟

Code

###Rust

impl Solution {
    pub fn reverse_submatrix(mut grid: Vec<Vec<i32>>, x: i32, y: i32, k: i32) -> Vec<Vec<i32>> {
        let (x, y, k) = (x as usize, y as usize, k as usize);
        for u in x..x + k / 2 {
            let d = x + k - 1 - u + x;
            for i in y..y + k {
                let p1 = &mut grid[u][i] as *mut i32;
                let p2 = &mut grid[d][i] as *mut i32;
                unsafe { std::ptr::swap(p1, p2); }
            }
        }

        grid
    }
}

双指针(Python/Java/C++/Go)

作者 endlesscheng
2025年8月10日 13:44

根据题意,交换的范围是行号 $[x,x+k-1]$,列号 $[y,y+k-1]$。

类似 344. 反转字符串,用双指针实现:

  • 初始化 $l=x$,$r=x+k-1$。
  • 循环直到 $l\ge r$。
  • 每次循环,对于 $[y,y+k-1]$ 中的每个整数 $j$,交换 $\textit{grid}[l][j]$ 和 $\textit{grid}[r][j]$。

具体请看 视频讲解,欢迎点赞关注~

###py

class Solution:
    def reverseSubmatrix(self, grid: List[List[int]], x: int, y: int, k: int) -> List[List[int]]:
        l, r = x, x + k - 1
        while l < r:
            for j in range(y, y + k):
                grid[l][j], grid[r][j] = grid[r][j], grid[l][j]
            l += 1
            r -= 1
        return grid

###py

class Solution:
    def reverseSubmatrix(self, grid: List[List[int]], x: int, y: int, k: int) -> List[List[int]]:
        l, r = x, x + k - 1
        while l < r:
            grid[l][y: y + k], grid[r][y: y + k] = grid[r][y: y + k], grid[l][y: y + k]
            l += 1
            r -= 1
        return grid

###java

class Solution {
    public int[][] reverseSubmatrix(int[][] grid, int x, int y, int k) {
        int l = x;
        int r = x + k - 1;
        while (l < r) {
            for (int j = y; j < y + k; j++) {
                int tmp = grid[l][j];
                grid[l][j] = grid[r][j];
                grid[r][j] = tmp;
            }
            l++;
            r--;
        }
        return grid;
    }
}

###cpp

class Solution {
public:
    vector<vector<int>> reverseSubmatrix(vector<vector<int>>& grid, int x, int y, int k) {
        int l = x, r = x + k - 1;
        while (l < r) {
            for (int j = y; j < y + k; j++) {
                swap(grid[l][j], grid[r][j]);
            }
            l++;
            r--;
        }
        return grid;
    }
};

###go

func reverseSubmatrix(grid [][]int, x, y, k int) [][]int {
l, r := x, x+k-1
for l < r {
for j := y; j < y+k; j++ {
grid[l][j], grid[r][j] = grid[r][j], grid[l][j]
}
l++
r--
}
return grid
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(k^2)$。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

模拟

作者 tsreaper
2025年8月10日 12:11

解法:模拟

按题意模拟即可。复杂度 $\mathcal{O}(nm)$。

参考代码(c++)

class Solution {
public:
    vector<vector<int>> reverseSubmatrix(vector<vector<int>>& grid, int x, int y, int k) {
        for (int i = x, ii = x + k - 1; i < ii; i++, ii--) for (int j = y; j < y + k; j++)
            swap(grid[i][j], grid[ii][j]);
        return grid;
    }
};
昨天以前LeetCode 每日一题题解

每日一题-子矩阵的最小绝对差🟡

2026年3月20日 00:00

给你一个 m x n 的整数矩阵 grid 和一个整数 k

对于矩阵 grid 中的每个连续的 k x k 子矩阵,计算其中任意两个 不同值 之间的 最小绝对差 

返回一个大小为 (m - k + 1) x (n - k + 1) 的二维数组 ans,其中 ans[i][j] 表示以 grid 中坐标 (i, j) 为左上角的子矩阵的最小绝对差。

注意:如果子矩阵中的所有元素都相同,则答案为 0。

子矩阵 (x1, y1, x2, y2) 是一个由选择矩阵中所有满足 x1 <= x <= x2y1 <= y <= y2 的单元格 matrix[x][y] 组成的矩阵。

 

示例 1:

输入: grid = [[1,8],[3,-2]], k = 2

输出: [[2]]

解释:

  • 只有一个可能的 k x k 子矩阵:[[1, 8], [3, -2]]
  • 子矩阵中的不同值为 [1, 8, 3, -2]
  • 子矩阵中的最小绝对差为 |1 - 3| = 2。因此,答案为 [[2]]

示例 2:

输入: grid = [[3,-1]], k = 1

输出: [[0,0]]

解释:

  • 每个 k x k 子矩阵中只有一个不同的元素。
  • 因此,答案为 [[0, 0]]

示例 3:

输入: grid = [[1,-2,3],[2,3,5]], k = 2

输出: [[1,2]]

解释:

  • 有两个可能的 k × k 子矩阵:
    • (0, 0) 为起点的子矩阵:[[1, -2], [2, 3]]
      • 子矩阵中的不同值为 [1, -2, 2, 3]
      • 子矩阵中的最小绝对差为 |1 - 2| = 1
    • (0, 1) 为起点的子矩阵:[[-2, 3], [3, 5]]
      • 子矩阵中的不同值为 [-2, 3, 5]
      • 子矩阵中的最小绝对差为 |3 - 5| = 2
  • 因此,答案为 [[1, 2]]

 

提示:

  • 1 <= m == grid.length <= 30
  • 1 <= n == grid[i].length <= 30
  • -105 <= grid[i][j] <= 105
  • 1 <= k <= min(m, n)

3567. 子矩阵的最小绝对差

作者 stormsunshine
2025年6月1日 15:26

解法

思路和算法

矩阵 $\textit{grid}$ 的大小是 $m \times n$,需要计算每个 $k \times k$ 的子矩阵中的任意两个不同值之间的最小绝对差。为了得到最小绝对差,需要得到 $k \times k$ 的子矩阵中的所有元素值,由于最小绝对差一定出现在排序后的两个相邻的不同元素值的差值中,因此需要将子矩阵中的元素值排序,然后计算最小绝对差。

创建 $(m - k + 1) \times (n - k + 1)$ 的二维数组 $\textit{ans}$,计算 $\textit{ans}[i][j]$ 的方法如下。

  1. 遍历矩阵 $\textit{grid}$ 的行下标范围 $[i, i + k - 1]$ 和列下标范围 $[j, j + k - 1]$ 中的 $k^2$ 个元素,使用长度为 $k^2$ 的数组存储。

  2. 将长度为 $k^2$ 的数组按升序排序,排序之后遍历数组中的每一对相邻元素,如果相邻元素不相等则计算绝对差。遍历结束之后即可得到当前子矩阵中的不同值之间的最小绝对差,填入 $\textit{ans}[i][j]$。

分别计算所有 $k \times k$ 的子矩阵的不同值之间的最小绝对差之后,二维数组 $\textit{ans}$ 即为答案。

代码

###Java

class Solution {
    public int[][] minAbsDiff(int[][] grid, int k) {
        int m = grid.length, n = grid[0].length;
        int[][] ans = new int[m - k + 1][n - k + 1];
        for (int i = 0; i < m - k + 1; i++) {
            for (int j = 0; j < n - k + 1; j++) {
                ans[i][j] = getMinAbsDiff(grid, k, i, j);
            }
        }
        return ans;
    }

    public int getMinAbsDiff(int[][] grid, int k, int startRow, int startCol) {
        int total = k * k;
        int[] values = new int[total];
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k; j++) {
                values[i * k + j] = grid[startRow + i][startCol + j];
            }
        }
        Arrays.sort(values);
        int minAbsDiff = Integer.MAX_VALUE;
        for (int i = 1; i < total; i++) {
            if (values[i] > values[i - 1]) {
                minAbsDiff = Math.min(minAbsDiff, values[i] - values[i - 1]);
            }
        }
        return minAbsDiff != Integer.MAX_VALUE ? minAbsDiff : 0;
    }
}

###C#

public class Solution {
    public int[][] MinAbsDiff(int[][] grid, int k) {
        int m = grid.Length, n = grid[0].Length;
        int[][] ans = new int[m - k + 1][];
        for (int i = 0; i < m - k + 1; i++) {
            ans[i] = new int[n - k + 1];
            for (int j = 0; j < n - k + 1; j++) {
                ans[i][j] = GetMinAbsDiff(grid, k, i, j);
            }
        }
        return ans;
    }

    public int GetMinAbsDiff(int[][] grid, int k, int startRow, int startCol) {
        int total = k * k;
        int[] values = new int[total];
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < k; j++) {
                values[i * k + j] = grid[startRow + i][startCol + j];
            }
        }
        Array.Sort(values);
        int minAbsDiff = int.MaxValue;
        for (int i = 1; i < total; i++) {
            if (values[i] > values[i - 1]) {
                minAbsDiff = Math.Min(minAbsDiff, values[i] - values[i - 1]);
            }
        }
        return minAbsDiff != int.MaxValue ? minAbsDiff : 0;
    }
}

复杂度分析

  • 时间复杂度:$O(mnk^2 \log k)$,其中 $m$ 和 $n$ 分别是矩阵 $\textit{grid}$ 的行数和列数,$k$ 是给定的子矩阵边长。需要计算的子矩阵个数是 $O(mn)$,对于每个子矩阵遍历 $k^2$ 个元素存入数组的时间是 $O(k^2)$,将数组排序的时间是 $O(k^2 \log k^2) = O(k^2 \log k)$,因此每个子矩阵的计算时间是 $O(k^2 \log k)$,时间复杂度是 $O(mnk^2 \log k)$。

  • 空间复杂度:$O(k^2)$,其中 $k$ 是给定的子矩阵边长。对于每个子矩阵计算差值时需要创建长度为 $k^2$ 的数组,将数组排序的空间是 $O(\log k^2) = O(\log k)$,因此空间复杂度是 $O(k^2)$。

暴力枚举(Python/Java/C++/Go)

作者 endlesscheng
2025年6月1日 13:51

暴力枚举所有子矩形。把子矩形中的所有元素添加到一个数组 $a$ 中,然后把 $a$ 排序。排序后,不同元素之差的最小值一定来自 $a$ 的相邻元素,计算相邻不同元素之差的最小值。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def minAbsDiff(self, grid: List[List[int]], k: int) -> List[List[int]]:
        m, n = len(grid), len(grid[0])
        ans = [[0] * (n - k + 1) for _ in range(m - k + 1)]
        for i in range(m - k + 1):
            sub_grid = grid[i: i + k]
            for j in range(n - k + 1):
                a = []
                for row in sub_grid:
                    a += row[j: j + k]
                a.sort()

                res = inf
                for x, y in pairwise(a):
                    if x < y:  # 题目要求相减的两个数必须不同
                        res = min(res, y - x)
                if res < inf:
                    ans[i][j] = res
        return ans

###java

class Solution {
    public int[][] minAbsDiff(int[][] grid, int k) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] ans = new int[m - k + 1][n - k + 1];
        int[] a = new int[k * k];
        for (int i = 0; i <= m - k; i++) {
            for (int j = 0; j <= n - k; j++) {
                int idx = 0;
                for (int x = 0; x < k; x++) {
                    for (int y = 0; y < k; y++) {
                        a[idx++] = grid[i + x][j + y];
                    }
                }
                Arrays.sort(a);

                int res = Integer.MAX_VALUE;
                for (int p = 1; p < a.length; p++) {
                    if (a[p] > a[p - 1]) { // 题目要求相减的两个数必须不同
                        res = Math.min(res, a[p] - a[p - 1]);
                    }
                }
                if (res < Integer.MAX_VALUE) {
                    ans[i][j] = res;
                }
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    vector<vector<int>> minAbsDiff(vector<vector<int>>& grid, int k) {
        int m = grid.size(), n = grid[0].size();
        vector ans(m - k + 1, vector<int>(n - k + 1));
        for (int i = 0; i <= m - k; i++) {
            for (int j = 0; j <= n - k; j++) {
                vector<int> a;
                for (int x = 0; x < k; x++) {
                    for (int y = 0; y < k; y++) {
                        a.push_back(grid[i + x][j + y]);
                    }
                }
                ranges::sort(a);

                int res = INT_MAX;
                for (int p = 1; p < a.size(); p++) {
                    if (a[p] > a[p - 1]) { // 题目要求相减的两个数必须不同
                        res = min(res, a[p] - a[p - 1]);
                    }
                }
                if (res < INT_MAX) {
                    ans[i][j] = res;
                }
            }
        }
        return ans;
    }
};

###go

func minAbsDiff(grid [][]int, k int) [][]int {
m, n := len(grid), len(grid[0])
ans := make([][]int, m-k+1)
arr := make([]int, k*k)
for i := range ans {
ans[i] = make([]int, n-k+1)
for j := range ans[i] {
a := arr[:0] // 避免反复 make
for _, row := range grid[i : i+k] {
a = append(a, row[j:j+k]...)
}
slices.Sort(a)

res := math.MaxInt
for p := 1; p < len(a); p++ {
if a[p] > a[p-1] { // 题目要求相减的两个数必须不同
res = min(res, a[p]-a[p-1])
}
}
if res < math.MaxInt {
ans[i][j] = res
}
}
}
return ans
}

复杂度分析

  • 时间复杂度:$\mathcal{O}((m-k)(n-k)k^2\log k)$,其中 $m$ 和 $n$ 分别为 $\textit{grid}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(k^2)$。返回值不计入。

:考虑用定长滑动窗口 + 有序集合 + 懒删除堆,用有序集合维护窗口(子矩阵)元素,用懒删除堆维护相邻不同元素之差。添加删除的时候更新相邻不同元素之差。

这样可以做到 $\mathcal{O}((m-k)nk\log k)$,但常数比较大。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

枚举

作者 tsreaper
2025年6月1日 12:12

解法:枚举

元素互不相同的序列中,两个元素的最小差值,就是序列排序后,相邻元素差值的最小值。

枚举所有子矩阵,将子矩阵里的元素排序并去重,求相邻元素差值的最小值即可。复杂度 $\mathcal{O}((n - k)(m - k)k^2\log k)$。

参考代码(c++)

class Solution {
public:
    vector<vector<int>> minAbsDiff(vector<vector<int>>& grid, int K) {
        int n = grid.size(), m = grid[0].size();

        // 求以 (SI, SJ) 为左上角的子矩阵的不同元素最小差值
        auto calc = [&](int SI, int SJ) {
            // 将子矩阵里的元素排序并去重
            vector<int> vec;
            for (int i = 0; i < K; i++) for (int j = 0; j < K; j++) vec.push_back(grid[SI + i][SJ + j]);
            sort(vec.begin(), vec.end());
            vec.resize(unique(vec.begin(), vec.end()) - vec.begin());
            // 特殊情况:只有一种元素
            if (vec.size() == 1) return 0;
            // 求i相邻元素差值的最小值
            int ret = 1e9;
            for (int i = 1; i < vec.size(); i++) ret = min(ret, vec[i] - vec[i - 1]);
            return ret;
        };

        vector<vector<int>> ans;
        // 枚举子矩阵的左上角
        for (int i = 0; i + K <= n; i++) {
            ans.push_back({});
            for (int j = 0; j + K <= m; j++) ans.back().push_back(calc(i, j));
        }
        return ans;
    }
};

每日一题-元素和小于等于 k 的子矩阵的数目🟡

2026年3月18日 00:00

给你一个下标从 0 开始的整数矩阵 grid 和一个整数 k

返回包含 grid 左上角元素、元素和小于或等于 k子矩阵的数目。

 

示例 1:

输入:grid = [[7,6,3],[6,6,1]], k = 18
输出:4
解释:如上图所示,只有 4 个子矩阵满足:包含 grid 的左上角元素,并且元素和小于或等于 18 。

示例 2:

输入:grid = [[7,2,9],[1,5,0],[2,6,6]], k = 20
输出:6
解释:如上图所示,只有 6 个子矩阵满足:包含 grid 的左上角元素,并且元素和小于或等于 20 。

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= n, m <= 1000
  • 0 <= grid[i][j] <= 1000
  • 1 <= k <= 109

元素和小于等于 k 的子矩阵的数目

2026年3月10日 12:43

方法一:二维前缀和

思路与算法

题目要求统计包含矩阵 $\textit{grid}$ 左上角元素的所有子矩阵中,元素和不超过 $k$ 的子矩阵个数。

我们从左上角出发,按行优先顺序遍历矩阵,将当前访问位置 $(i, j)$ 视为子矩阵的右下角。为了在一次遍历中高效计算子矩阵的和,我们维护一个数组 $\textit{cols}[j]$,用于记录当前行之前第 $j$ 列所有元素的和。在遍历第 $i$ 行时,按列从左到右遍历 $j$,将 $\textit{grid}[i][j]$ 累加到 $\textit{cols}[j]$后,并将 $\textit{cols}[j]$ 累加起来,若当前累加和 $\le k$,则答案加 $1$。

代码

###C++

class Solution {
public:
    int countSubmatrices(vector<vector<int>>& grid, int k) {
        int n = grid.size(), m = grid[0].size();
        vector<int> cols(m);
        int res = 0;
        for (int i = 0; i < n; i++) {
            int rows = 0;
            for (int j = 0; j < m; j++) {
                cols[j] += grid[i][j];
                rows += cols[j];
                if (rows <= k) {
                    res++;
                }
            }
        }
        return res;
    }
};

###Python

class Solution:
    def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
        n, m = len(grid), len(grid[0])
        cols = [0] * m
        res = 0
        
        for i in range(n):
            row_sum = 0
            for j in range(m):
                cols[j] += grid[i][j]
                row_sum += cols[j]
                if row_sum <= k:
                    res += 1
        
        return res

###Rust

impl Solution {
    pub fn count_submatrices(grid: Vec<Vec<i32>>, k: i32) -> i32 {
        let n = grid.len();
        let m = grid[0].len();
        let mut cols = vec![0; m];
        let mut res = 0;
        
        for i in 0..n {
            let mut row_sum = 0;
            for j in 0..m {
                cols[j] += grid[i][j];
                row_sum += cols[j];
                if row_sum <= k {
                    res += 1;
                }
            }
        }
        
        res
    }
}

###Java

class Solution {
    public int countSubmatrices(int[][] grid, int k) {
        int n = grid.length, m = grid[0].length;
        int[] cols = new int[m];
        int res = 0;
        
        for (int i = 0; i < n; i++) {
            int rows = 0;
            for (int j = 0; j < m; j++) {
                cols[j] += grid[i][j];
                rows += cols[j];
                if (rows <= k) {
                    res++;
                }
            }
        }
        
        return res;
    }
}

###C#

public class Solution {
    public int CountSubmatrices(int[][] grid, int k) {
        int n = grid.Length, m = grid[0].Length;
        int[] cols = new int[m];
        int res = 0;
        
        for (int i = 0; i < n; i++) {
            int rows = 0;
            for (int j = 0; j < m; j++) {
                cols[j] += grid[i][j];
                rows += cols[j];
                if (rows <= k) {
                    res++;
                }
            }
        }
        
        return res;
    }
}

###Go

func countSubmatrices(grid [][]int, k int) int {
    n := len(grid)
    m := len(grid[0])
    cols := make([]int, m)
    res := 0
    
    for i := 0; i < n; i++ {
        rows := 0
        for j := 0; j < m; j++ {
            cols[j] += grid[i][j]
            rows += cols[j]
            if rows <= k {
                res++
            }
        }
    }
    
    return res
}

###C

int countSubmatrices(int** grid, int gridSize, int* gridColSize, int k) {
    int n = gridSize;
    int m = *gridColSize;
    int* cols = (int*)calloc(m, sizeof(int));
    int res = 0;
    
    for (int i = 0; i < n; i++) {
        int rows = 0;
        for (int j = 0; j < m; j++) {
            cols[j] += grid[i][j];
            rows += cols[j];
            if (rows <= k) {
                res++;
            }
        }
    }
    
    free(cols);
    return res;
}

###JavaScript

var countSubmatrices = function(grid, k) {
    const n = grid.length;
    const m = grid[0].length;
    const cols = new Array(m).fill(0);
    let res = 0;
    
    for (let i = 0; i < n; i++) {
        let rows = 0;
        for (let j = 0; j < m; j++) {
            cols[j] += grid[i][j];
            rows += cols[j];
            if (rows <= k) {
                res++;
            }
        }
    }
    
    return res;
};

###TypeScript

function countSubmatrices(grid: number[][], k: number): number {
    const n: number = grid.length;
    const m: number = grid[0].length;
    const cols: number[] = new Array(m).fill(0);
    let res: number = 0;
    
    for (let i = 0; i < n; i++) {
        let rows: number = 0;
        for (let j = 0; j < m; j++) {
            cols[j] += grid[i][j];
            rows += cols[j];
            if (rows <= k) {
                res++;
            }
        }
    }
    
    return res;
}

复杂度分析

  • 时间复杂度:$O(mn)$,其中 $m$ 是 $\textit{grid}$ 的行数,$n$ 是 $\textit{grid}$ 的列数。

  • 空间复杂度:$O(n)$。

C++二位前缀和

作者 thdlrt
2024年3月3日 17:52

Problem: 100237. 元素和小于等于 k 的子矩阵的数目

思路

  • 非常标准的二位前缀和,处理之后对每个位置都可以在$O(1)$的时间完成一次查询,暴力即可

Code

###C++

class Solution {
public:
    int countSubmatrices(vector<vector<int>>& grid, int k) {
        int n=grid.size(),m=grid[0].size();
        vector<vector<int>>arr(n+1,vector<int>(m+1));
        int res=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                arr[i][j]=arr[i-1][j]+arr[i][j-1]-arr[i-1][j-1]+grid[i-1][j-1];
                if(arr[i][j]<=k)
                    res++;
            }
        }
        return res;
    }
};

两种方法:二维前缀和模板 / 维护每列元素和(Python/Java/C++/Go)

作者 endlesscheng
2024年3月3日 12:11

方法一:二维前缀和

前置知识【图解】二维前缀和

本题相当于统计有多少个二维前缀和 $\le k$。

###py

class Solution:
    def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
        m, n = len(grid), len(grid[0])
        s = [[0] * (n + 1) for _ in range(m + 1)]
        ans = 0
        for i, row in enumerate(grid):
            for j, x in enumerate(row):
                s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + x
                if s[i + 1][j + 1] <= k:
                    ans += 1
        return ans

###java

class Solution {
    public int countSubmatrices(int[][] grid, int k) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] sum = new int[m + 1][n + 1];
        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + grid[i][j];
                if (sum[i + 1][j + 1] <= k) {
                    ans++;
                }
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int countSubmatrices(vector<vector<int>>& grid, int k) {
        int m = grid.size(), n = grid[0].size();
        vector sum(m + 1, vector<int>(n + 1));
        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + grid[i][j];
                ans += sum[i + 1][j + 1] <= k;
            }
        }
        return ans;
    }
};

###go

func countSubmatrices(grid [][]int, k int) (ans int) {
m, n := len(grid), len(grid[0])
sum := make([][]int, m+1)
sum[0] = make([]int, n+1)
for i, row := range grid {
sum[i+1] = make([]int, n+1)
for j, x := range row {
sum[i+1][j+1] = sum[i+1][j] + sum[i][j+1] - sum[i][j] + x
if sum[i+1][j+1] <= k {
ans++
}
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{grid}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(mn)$。

:如果原地计算二维前缀和,可以做到 $\mathcal{O}(1)$ 额外空间。

方法二:维护每列的元素和

遍历每一行,同时用一个长为 $n$ 的数组 $\textit{colSum}$ 维护每一列的元素和。

遍历当前行时,一边更新 $\textit{colSum}[j]$,一边累加 $\textit{colSum}[j]$ 到变量 $s$ 中。

如果 $s\le k$ 则把答案加一,否则可以跳出循环(因为矩阵元素都非负)。

###py

class Solution:
    def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
        col_sum = [0] * len(grid[0])
        ans = 0
        for row in grid:
            s = 0
            for j, x in enumerate(row):
                col_sum[j] += x
                s += col_sum[j]
                if s > k:
                    break
                ans += 1
        return ans

###java

class Solution {
    public int countSubmatrices(int[][] grid, int k) {
        int n = grid[0].length;
        int[] colSum = new int[n];
        int ans = 0;
        for (int[] row : grid) {
            int s = 0;
            for (int j = 0; j < n; j++) {
                colSum[j] += row[j];
                s += colSum[j];
                if (s > k) {
                    break;
                }
                ans++;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int countSubmatrices(vector<vector<int>>& grid, int k) {
        int n = grid[0].size();
        vector<int> col_sum(n);
        int ans = 0;
        for (auto& row : grid) {
            int s = 0;
            for (int j = 0; j < n; j++) {
                col_sum[j] += row[j];
                s += col_sum[j];
                if (s > k) {
                    break;
                }
                ans++;
            }
        }
        return ans;
    }
};

###go

func countSubmatrices(grid [][]int, k int) (ans int) {
colSum := make([]int, len(grid[0]))
for _, row := range grid {
s := 0
for j, x := range row {
colSum[j] += x
s += colSum[j]
if s > k {
break
}
ans++
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{grid}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(n)$。

:如果把每列元素和保存到 $\textit{grid}$ 的第一行,可以做到 $\mathcal{O}(1)$ 额外空间。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-重新排列后的最大子矩阵🟡

2026年3月17日 00:00

给你一个二进制矩阵 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 <= 105
  • matrix[i][j] 要么是 0 ,要么是 1

枚举子矩形的底边 + O(mn) 优化(Python/Java/C++/Go)

作者 endlesscheng
2026年3月10日 22:22

做法类似 85. 最大矩形,枚举子矩形的底边(最后一行),定义 $\textit{heights}[j]$ 表示从 $\textit{matrix}[i][j]$ 往上有多少个连续的 $1$(柱子的高度),问题变成:

  • 你可以重排 $\textit{heights}$。重排后,对于 $\textit{heights}$ 的连续子数组,子数组长度(矩形底边长)$\times$ 子数组最小值(矩形的高),即为全 $1$ 子矩形的面积。

对于示例 1,以第三行为底边算出来的 $\textit{heights} = [2,0,3]$,下图重排后是 $[2,3,0]$。其中子数组 $[2,3]$,长为 $2$,最小值为 $2$,所以对应的子矩形面积为 $2\times 2 = 4$。

lc1727.png{:width=430px}

如何找到面积最大的子矩形?还是枚举。

枚举子数组的长度 $k = 1,2,\ldots,n$。由于我们可以重排 $\textit{heights}$,那么贪心地,把 $\textit{heights}$ 最大的 $k$ 个数排在一起,就可以让子数组的最小值(矩形的高)尽量大,从而得到最大的矩形面积。

对于 $\textit{heights}$ 的计算,如果 $\textit{matrix}[i][j]=0$,那么 $\textit{heights}[j] = 0$。否则,把高度增加 $1$。形象地说,就是在柱子下面垫一块石头,把柱子抬高。

优化前

###py

class Solution:
    def largestSubmatrix(self, matrix: List[List[int]]) -> int:
        n = len(matrix[0])
        heights = [0] * n
        ans = 0

        for row in matrix:  # 枚举子矩形的底边
            for j, x in enumerate(row):
                if x == 0:
                    heights[j] = 0
                else:
                    heights[j] += 1

            hs = sorted(heights)  # 复制一份 heights 再排序
            for i, h in enumerate(hs):  # 把 hs[i:] 作为子数组
                # 子数组长为 n-i,最小值为 h,对应的子矩形面积为 (n-i)*h
                ans = max(ans, (n - i) * h)  

        return ans

###java

class Solution {
    public int largestSubmatrix(int[][] matrix) {
        int n = matrix[0].length;
        int[] heights = new int[n];
        int ans = 0;

        for (int[] row : matrix) { // 枚举子矩形的底边
            for (int j = 0; j < n; j++) {
                if (row[j] == 0) {
                    heights[j] = 0;
                } else {
                    heights[j]++;
                }
            }

            int[] hs = heights.clone();
            Arrays.sort(hs);
            for (int i = 0; i < n; i++) { // 把 [i,n-1] 作为子数组
                // 子数组长为 n-i,最小值为 hs[i],对应的子矩形面积为 (n-i)*hs[i]
                ans = Math.max(ans, (n - i) * hs[i]); 
            }
        }

        return ans;
    }
}

###cpp

class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int n = matrix[0].size();
        vector<int> heights(n);
        int ans = 0;

        for (auto& row : matrix) { // 枚举子矩形的底边
            for (int j = 0; j < n; j++) {
                int x = row[j];
                if (x == 0) {
                    heights[j] = 0;
                } else {
                    heights[j]++;
                }
            }

            auto hs = heights;
            ranges::sort(hs);
            for (int i = 0; i < n; i++) { // 把 [i,n-1] 作为子数组
                // 子数组长为 n-i,最小值为 hs[i],对应的子矩形面积为 (n-i)*hs[i]
                ans = max(ans, (n - i) * hs[i]); 
            }
        }
        return ans;
    }
};

###go

func largestSubmatrix(matrix [][]int) (ans int) {
n := len(matrix[0])
heights := make([]int, n)

for _, row := range matrix { // 枚举子矩形的底边
for j, x := range row {
if x == 0 {
heights[j] = 0
} else {
heights[j]++
}
}

hs := slices.Clone(heights)
slices.Sort(hs)
for i, h := range hs { // 把 hs[i:] 作为子数组
ans = max(ans, (n-i)*h) // 子数组长为 n-i,最小值为 h,对应的子矩形面积为 (n-i)*h
}
}

return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn\log n)$,其中 $m$ 和 $n$ 分别是 $\textit{matrix}$ 的行数和列数。瓶颈在排序上,有 $m$ 行,每行都要跑一个 $\mathcal{O}(n\log n)$ 的排序。
  • 空间复杂度:$\mathcal{O}(n)$。

优化

考察从 $i-1$ 行到 $i$ 行,$\textit{heights}$ 会如何变化:

  • 如果 $\textit{matrix}[i][j] = 0$,那么 $\textit{heights}[j] = 0$。在排序后,$0$ 会排在大于 $0$ 的高度前面。
  • 如果 $\textit{matrix}[i][j] = 1$,那么 $\textit{heights}[j]$ 增加一。对于那些增加一的高度,相对大小是不变的,无需再次排序。比如把 $1,2,3$ 都增加一,得到 $2,3,4$,这三个数的相对大小不变。

举个例子。假设 $i-1$ 行的 $\textit{heights}$ 排序后是 $[0,{\color{red}0},{\color{red}0},1,{\color{red}2},{\color{red}3}]$,把红色数字加一,其余数字变成 $0$,得到 $[0,{\color{red}1},{\color{red}1},0,{\color{red}3},{\color{red}4}]$。把 $0$ 排在红色数字前面,得到 $[0,0,{\color{red}1},{\color{red}1},{\color{red}3},{\color{red}4}]$。注意红色数字的相对大小是不变的,无需再次排序。

一般地,如果已知 $i-1$ 行的 $\textit{heights}$ 排序后的结果,那么对于 $i$ 行,我们只需把高度变成 $0$ 的数据排在前面,其余(增加一的)高度的相对大小不变,无需再次排序。这样就可以把排序的时间从 $\mathcal{O}(n\log n)$ 优化成 $\mathcal{O}(n)$。

但是,如果直接对 $\textit{heights}$ 排序,我们就不知道每个高度对应矩阵的哪一列了。如何解决?创建一个 $0$ 到 $n-1$ 的下标数组(列号数组)$\textit{idx}$,对下标数组排序。

###py

class Solution:
    def largestSubmatrix(self, matrix: List[List[int]]) -> int:
        n = len(matrix[0])
        heights = [0] * n
        idx = list(range(n))  # 按照高度排序后的列号
        ans = 0

        for row in matrix:
            zeros = []
            non_zeros = []
            for j in idx:
                if row[j] == 0:
                    heights[j] = 0
                    zeros.append(j)
                else:
                    heights[j] += 1
                    non_zeros.append(j)
            idx = zeros + non_zeros  # 把高度为 0 的列号排在其他高度前面

            # heights[idx[i]] 是递增的
            for i in range(len(zeros), n):  # 高度 0 无需计算
                ans = max(ans, (n - i) * heights[idx[i]])

        return ans

###java

class Solution {
    public int largestSubmatrix(int[][] matrix) {
        int n = matrix[0].length;
        int[] heights = new int[n];
        int[] idx = new int[n]; // 按照高度排序后的列号
        for (int i = 0; i < n; i++) {
            idx[i] = i;
        }
        int[] nonZeros = new int[n]; // 避免在循环内反复申请内存
        int ans = 0;

        for (int[] row : matrix) {
            int p = 0;
            int q = 0;
            for (int j : idx) {
                if (row[j] == 0) {
                    heights[j] = 0;
                    idx[p++] = j; // 高度 0 排在前面
                } else {
                    heights[j]++;
                    nonZeros[q++] = j;
                }
            }

            // heights[idx[i]] 是递增的
            for (int i = p; i < n; i++) { // 高度 0 无需计算
                idx[i] = nonZeros[i - p]; // 把 nonZeros 复制到 idx 的 [p,n-1] 中
                ans = Math.max(ans, (n - i) * heights[idx[i]]);
            }
        }

        return ans;
    }
}

###cpp

class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int n = matrix[0].size();
        vector<int> heights(n);
        vector<int> idx(n); // 按照高度排序后的列号
        ranges::iota(idx, 0); // idx[i] = i
        vector<int> non_zeros(n); // 避免在循环内反复申请内存
        int ans = 0;

        for (auto& row : matrix) {
            int p = 0, q = 0;
            for (int j : idx) {
                if (row[j] == 0) {
                    heights[j] = 0;
                    idx[p++] = j; // 高度 0 排在前面
                } else {
                    heights[j]++;
                    non_zeros[q++] = j;
                }
            }

            // heights[idx[i]] 是递增的
            for (int i = p; i < n; i++) { // 高度 0 无需计算
                idx[i] = non_zeros[i - p]; // 把 non_zeros 复制到 idx 的 [p,n-1] 中
                ans = max(ans, (n - i) * heights[idx[i]]);
            }
        }

        return ans;
    }
};

###go

func largestSubmatrix(matrix [][]int) (ans int) {
n := len(matrix[0])
heights := make([]int, n)
idx := make([]int, n) // 按照高度排序后的列号
for i := range idx {
idx[i] = i
}
_nonZeros := make([]int, n) // 避免在循环内反复申请内存

for _, row := range matrix {
zeros := idx[:0]
nonZeros := _nonZeros[:0]
for _, j := range idx {
if row[j] == 0 {
heights[j] = 0
zeros = append(zeros, j)
} else {
heights[j]++
nonZeros = append(nonZeros, j)
}
}
idx = append(zeros, nonZeros...) // 把高度为 0 的列号排在其他高度前面

// heights[idx[i]] 是递增的
for i := len(zeros); i < n; i++ { // 高度 0 无需计算
ans = max(ans, (n-i)*heights[idx[i]])
}
}

return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{matrix}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(n)$。

专题训练

见下面贪心题单的「§1.6 先枚举,再贪心」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

【贪心】活学活用,做题不慌

作者 Arsenal-591
2021年1月17日 12:22

提示:如果你没有做出来这道题,建议先去回顾一下 85.最大矩形 的思想。本质上,这类题目都是通过枚举其中一个维度,将问题划归为一维问题来进行求解。

设矩阵为 $H$ 行 $W$ 列。

首先,我们维护一个等大的数组 $\textit{up}$,其中 $\textit{up}[i][j]$ 表示 $\textit{matrix}[i][j]$ 上面有多少个 $1$(包括它自己)。

随后,我们枚举最大矩形的底部位置 $i$。由于列之间可以任意排序,所以可以按照 $\textit{up}[i][0], \textit{up}[i][1], ... , \textit{up}[i][W-1]$ 的大小进行递增排序。

在递增排序过后,设(排序后的)第 $i$ 行第 $1$ 列上面有 $a_1$ 个 $1$。由于已经递增排序,所以第 $i$ 行第 $1$ 列右面的所有位置的上面都至少有 $a_1$ 个 $1$。于是,底边为第 $i$ 行,高度为 $a_1$ 的矩阵的最大宽度为 $W$,对应面积为 $a_1W$。

同理,设第 $i$ 行第 $2$ 列上面有 $a_2$ 个 $1$,则第 $i$ 行第 $1$ 列右面的所有位置的上面都至少有 $a_2$ 个 $1$,因此对应面积 $a_2(W-1)$。以此类推,我们能够得到底边为第 $i$ 行的矩形的最大面积。

随后,再枚举所有的 $i$,就可以得到整体的最大面积。

class Solution {
public:
    int largestSubmatrix(vector<vector<int>>& matrix) {
        int h = matrix.size(), w = matrix[0].size();
        vector<vector<int>> up(h, vector<int>(w, 0));
        
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                if (matrix[i][j] == 1) {
                    up[i][j] = (i == 0 ? 0 : up[i-1][j]) + 1;
                }
            }
        }

        int ret = 0;
        for (int i = 0; i < h; i++) {
            vector<int> buf;
            for (int j = 0; j < w; j++) {
                buf.push_back(up[i][j]);
            }
            sort(buf.begin(), buf.end());
            for (int j = 0; j < w; j++) {
                ret = max(ret, buf[j] * (w - j));
            }
        }
        return ret;
    }
};

时间复杂度
共枚举 $H$ 行,每行需要 $O(W\log W)$ 的排序以及 $O(W)$ 的额外扫描,故总体复杂度为 $O(HW\log W)$。

❌
❌