LeetCode 热题 100 精讲 | 矩阵与图论进阶篇:矩阵置零 · 螺旋矩阵 · 旋转图像 · 搜索二维矩阵 II · 岛屿数量 · 腐烂的橘子

一、73. 矩阵置零

🔗 题目链接

LeetCode 73. 矩阵置零

📝 题目描述

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例

复制代码
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]

🧠 思路分析

最直观的想法是用两个布尔数组记录哪些行和列需要置零,空间复杂度 O(m + n)。但题目要求原地算法,所以需要利用矩阵本身来存储标记信息。一个巧妙的做法是:用第一行和第一列作为标记数组。先单独记录第一行和第一列本身是否包含 0,然后遍历除了第一行第一列之外的矩阵,如果某个位置是 0,就把对应行的第一个元素和对应列的第一个元素标记为 0。第二次遍历时,根据这些标记来置零。最后再处理第一行和第一列的标记。这样空间复杂度降到了 O(1)。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        bool firstRowZero = false, firstColZero = false;
        
        // 检查第一行是否有0
        for (int j = 0; j < n; j++) {
            if (matrix[0][j] == 0) firstRowZero = true;
        }
        // 检查第一列是否有0
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) firstColZero = true;
        }
        // 用第一行和第一列作为标记
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = 0;
                    matrix[0][j] = 0;
                }
            }
        }
        // 根据标记置零
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        // 处理第一行
        if (firstRowZero) {
            for (int j = 0; j < n; j++) matrix[0][j] = 0;
        }
        // 处理第一列
        if (firstColZero) {
            for (int i = 0; i < m; i++) matrix[i][0] = 0;
        }
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m×n),两次遍历矩阵。

  • 空间复杂度:O(1),只用了常数个变量。

二、54. 螺旋矩阵

🔗 题目链接

LeetCode 54. 螺旋矩阵

📝 题目描述

给你一个 mn 列的矩阵 matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例

复制代码
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

🧠 思路分析

螺旋遍历的核心在于控制四个边界:上边界 top、下边界 bottom、左边界 left、右边界 right。按顺时针顺序遍历:先从左到右遍历上边界(top 不变,leftright),然后上边界 top++;再从上到下遍历右边界(right 不变,topbottom),然后右边界 right--;如果 top <= bottom,继续从右到左遍历下边界;如果 left <= right,继续从下到上遍历左边界。每遍历完一条边,收缩对应的边界,直到所有元素都被访问。这种方法直观且容易调试,边界条件的处理是关键。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        if (matrix.empty()) return res;
        int top = 0, bottom = matrix.size() - 1;
        int left = 0, right = matrix[0].size() - 1;
        while (top <= bottom && left <= right) {
            // 从左到右遍历上边界
            for (int j = left; j <= right; j++) res.push_back(matrix[top][j]);
            top++;
            // 从上到下遍历右边界
            for (int i = top; i <= bottom; i++) res.push_back(matrix[i][right]);
            right--;
            if (top <= bottom) {
                // 从右到左遍历下边界
                for (int j = right; j >= left; j--) res.push_back(matrix[bottom][j]);
                bottom--;
            }
            if (left <= right) {
                // 从下到上遍历左边界
                for (int i = bottom; i >= top; i--) res.push_back(matrix[i][left]);
                left++;
            }
        }
        return res;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m×n),每个元素被访问一次。

  • 空间复杂度:O(1),不计输出数组。

三、48. 旋转图像

🔗 题目链接

LeetCode 48. 旋转图像

📝 题目描述

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例

复制代码
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

🧠 思路分析

不能使用额外矩阵,就需要找到元素之间的映射规律。观察发现,顺时针旋转 90 度等价于两步操作:先沿主对角线翻转(转置),再沿垂直中线翻转(左右镜像)。或者先上下翻转再转置也能达到同样效果。具体步骤:首先将矩阵转置,即 matrix[i][j]matrix[j][i] 交换;然后将每一行的元素左右翻转。这个解法利用了矩阵变换的分解思想,只需要 O(1) 的额外空间。面试中这是最推荐的写法,代码简洁且不易出错。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    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]);
            }
        }
        // 再左右镜像
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n / 2; j++) {
                swap(matrix[i][j], matrix[i][n - 1 - j]);
            }
        }
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(n²),两次遍历矩阵。

  • 空间复杂度:O(1),原地交换。

四、240. 搜索二维矩阵 II

🔗 题目链接

LeetCode 240. 搜索二维矩阵 II

📝 题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:每行的元素从左到右升序排列,每列的元素从上到下升序排列。

示例

复制代码
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

🧠 思路分析

利用矩阵的特殊性质,可以从右上角或左下角开始搜索。以右上角为例,当前元素是所在行的最大值、所在列的最小值。如果当前元素大于 target,说明当前列的所有元素都大于 target,可以向左移动一列;如果当前元素小于 target,说明当前行的所有元素都小于 target,可以向下移动一行;如果相等则找到目标。这样每次比较都能排除一行或一列,时间复杂度降为 O(m + n)。暴力解法 O(m×n) 在数据量大的时候会超时。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int i = 0, j = n - 1;  // 从右上角开始
        while (i < m && j >= 0) {
            if (matrix[i][j] == target) return true;
            if (matrix[i][j] > target) j--;
            else i++;
        }
        return false;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m + n),每次比较排除一行或一列。

  • 空间复杂度:O(1)。

五、200. 岛屿数量

🔗 题目链接

LeetCode 200. 岛屿数量

📝 题目描述

给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。你可以假设网格的四个边均被水包围。

示例

复制代码
输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

🧠 思路分析

遍历网格,每遇到一个 '1' 就计数加一,然后用 DFS 或 BFS 把这个岛屿中所有相连的陆地标记为已访问(比如置为 '0' 或用 visited 数组)。DFS 递归地访问上下左右四个方向,遇到边界或水就返回。BFS 用队列记录每个陆地节点的位置,层层扩散。两种方法都能在 O(m×n) 时间内解决问题。如果数据量特别大,递归的 DFS 可能因为栈深度过深而溢出,这时 BFS 更安全。面试中两种写法最好都掌握。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int count = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    count++;
                    dfs(grid, i, j);
                }
            }
        }
        return count;
    }

    void dfs(vector<vector<char>>& grid, int i, int j) {
        if (i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size() || grid[i][j] != '1') return;
        grid[i][j] = '0';  // 标记为已访问
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m×n),每个格子最多被访问一次。

  • 空间复杂度:最坏 O(m×n),递归栈深度。

六、994. 腐烂的橘子

🔗 题目链接

LeetCode 994. 腐烂的橘子

📝 题目描述

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:值 0 代表空单元格;值 1 代表新鲜橘子;值 2 代表腐烂橘子。每分钟,腐烂的橘子会使其上下左右四个方向相邻的新鲜橘子腐烂。返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1

示例

复制代码
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4

🧠 思路分析

这是一道典型的多源 BFS 问题。初始时可能有多个腐烂的橘子,它们同时向四周扩散。用队列记录所有腐烂橘子的位置和当前时间,同时统计新鲜橘子的总数。BFS 每层扩散一次,时间加一,腐烂一个新鲜橘子就减少新鲜橘子计数。当队列为空时,如果新鲜橘子数量为 0,则返回扩散的总时间;否则返回 -1。多源 BFS 的关键在于一开始就把所有腐烂橘子都放入队列,这样它们会同步扩散,模拟了"每分钟"的真实过程。

💻 代码实现(C++)

cpp 复制代码
class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        queue<pair<int, int>> q;
        int fresh = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 2) q.push({i, j});
                else if (grid[i][j] == 1) fresh++;
            }
        }
        if (fresh == 0) return 0;
        int dirs[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};
        int minutes = -1;
        while (!q.empty()) {
            int size = q.size();
            minutes++;
            for (int i = 0; i < size; i++) {
                auto [x, y] = q.front(); q.pop();
                for (auto& dir : dirs) {
                    int nx = x + dir[0], ny = y + dir[1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
                        grid[nx][ny] = 2;
                        fresh--;
                        q.push({nx, ny});
                    }
                }
            }
        }
        return fresh == 0 ? minutes : -1;
    }
};

📚 相关学习资源

免责声明:以上链接均来自公开网络。若存在侵权问题,请联系删除。

⏱ 复杂度分析

  • 时间复杂度:O(m×n),每个格子最多入队一次。

  • 空间复杂度:O(m×n),队列在最坏情况下存储所有格子。

结语

矩阵与图论进阶篇的六道题覆盖了矩阵操作和图论 BFS/DFS 的核心技巧。矩阵置零 考察原地算法的标记技巧;螺旋矩阵旋转图像 考验对矩阵边界和变换规律的把握;搜索二维矩阵 II 利用有序特性用双指针高效查找;岛屿数量 是图论连通性问题的基础;腐烂的橘子则是多源 BFS 的经典应用。把这六道题吃透,面试中矩阵和图论类的题目基本都能应对。

下一篇将进入 栈、队列与堆的妙用篇(有效的括号、最小栈、字符串解码、每日温度、接雨水、数组中的第K个最大元素等),敬请期待。

如果本文对你有帮助,欢迎点赞、收藏、转发,你的支持是我持续创作的动力 ❤️

免责声明:本文部分解题思路参考了力扣官方题解及社区优秀文章,相关链接均来自公开网络。若存在侵权问题,请联系删除。

相关推荐
Ailan_Anjuxi2 小时前
【附jupyter源码】使用长短期记忆网络(LSTM)实现一个小说写作AI——以训练《西游记》为例
人工智能·算法
stolentime2 小时前
线段树套?——洛谷P7312 [COCI 2018/2019 #2] Sunčanje题解
c++·算法·图论·洛谷
wayz112 小时前
Day 12:支持向量机(SVM)原理与实践
算法·机器学习·支持向量机
EverestVIP2 小时前
c++ 的terminate()函数
c++
郝学胜-神的一滴2 小时前
干货版《算法导论》 01:从问题定义到正确性证明
数据结构·人工智能·深度学习·神经网络·算法·机器学习
大肥羊学校懒羊羊2 小时前
特殊乘法的计算
数据结构·c++·算法
IronMurphy2 小时前
【算法四十一】763. 划分字母区间
算法
cpp_25012 小时前
P2430 严酷的训练
数据结构·c++·算法·动态规划·洛谷·背包dp
Rabitebla2 小时前
【数据结构】实现通讯录:基于C语言动态顺序表
c语言·开发语言·数据结构·算法