矩阵 - LeetCode hot 100

矩阵 - LeetCode hot 100

@jarringslee

文章目录

二维数组的特性

二维数组在几何层面可以被看做一个矩阵。在算法题的考察中一般为矩阵的遍历、边界控制以及原地修改矩阵,难点不在于数据结构和语法的考察,而在于几何规律、自身特性的利用和边界条件的控制。

大多数题目的做题套路都比较固定,我们需要先获取矩阵的大小和单个成员的大小,即矩阵的行数和列数,然后通过遍历来进行搜索、修改等操作。

18 73. 矩阵置零

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

复制代码
输入: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]]

本题的基本思路是遍历矩阵,如果发现某行或某列的数字为0,那就把该数字所在的行和列都变成0。但是如果直接遍历矩阵,遇到0就将对应行和列全部置0。但这样会导致后续遍历误把新产生的0当成原始0,造成错误。因此需要额外空间记录哪些行和列需要置0。

最暴力的解法是复制一个矩阵,一边遍历原矩阵一边在新矩阵中填充信息,遇到0就在新矩阵相应的行和列中填充0。但是这样的空间复杂度为 m×n。

使用额外数组记录行和列

为了优化上面的思想,我们直接使用两个数组来记录矩阵中0的情况,然后直接修改原矩阵。

时间复杂度:O(m×n) 遍历m行n列

空间复杂度:O(m+n) 额外使用了两个记录数组

js 复制代码
/**
 * @param {number[][]} matrix
 * @return {void}  noDot return anything, modify matrix in-place instead.
 */
var setZeroes = function(matrix) {
    // 获取矩阵大小
    let m = matrix.length;
    let n = matrix[0].length;
    // 创建记录数组
    let rows = new Array(m).fill(false);
    let cols = new Array(n).fill(false);
    // 第一次遍历:寻找0
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (matrix[i][j] === 0) rows[i] = cols[j] = true;
        }
    }
    // 第二次遍历:根据数组信息修改矩阵
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (rows[i] || cols[j]) matrix[i][j] = 0;
        }
    } 
};

利用矩阵的首行首列

我们做算法题应当尽量地复用原有的数据结构 。我们可以让矩阵的首行和首列来充当判断信息的两个数组。我们提前将首行和首列是否有0的信息储存(因为第一行和第一列之后会被作为标记数组使用,如果它们原本包含0,后续无法区分这个0是原始信息还是标记信息,所以需要提前保存),然后从第二行、第二列开始循环遍历,遇到0就将其对应首行首列位置也置0,然后再次从第二行第二列开始遍历,根据首行首列储存的信息来进行置0。随后根据首行首列储存的信息将首行首列进行置0操作。

时间复杂度:O(m×n)

空间复杂度:O(1) 不使用额外数据结构

js 复制代码
/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function(matrix) {
    // 获取行列长度
    const m = matrix.length;
    const n = matrix[0].length;

    // 提前储存首行首列中是否有0的信息
    const firstRowHasZero = matrix[0].includes(0); // 首行直接使用include进行判断
    const firstColHasZero = matrix.some(row => row[0] === 0); // 首列无法使用include,使用some进行遍历判断

    // 跳过首行首列进行遍历:如果该位置为0就将该位置的对应首行和首列的位置置0
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            if (matrix[i][j] === 0) matrix[i][0] = matrix[0][j] = 0;
        }
    }

    // 跳过首行首列进行置0:如果该位置对应首行或首列的位置为0就将该位置置0
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            if (matrix[i][0] === 0 || matrix[0][j] === 0) matrix[i][j] = 0; 
        }
    }

    // 首列判断置0:遍历每行首位置0
    if (firstColHasZero) {
        for (const row of matrix) {
            row[0] = 0;
        }
    }
    // 首行判断置0:直接使用fill填充
    if (firstRowHasZero) matrix[0].fill(0);
};
js 复制代码
var setZeroes = function(matrix) {
    const m = matrix.length, n = matrix[0].length, row1 = matrix[0].includes(0), col1 = matrix.some(col => col[0] === 0);
    for (let i = 1; i < m; i++) for (let j = 1; j < n; j++) if (matrix[i][j] === 0) matrix[i][0] = matrix[0][j] = 0;
    for (let i = 1; i < m; i++) for (let j = 1; j < n; j++)  if (matrix[i][0] === 0 || matrix[0][j] === 0) matrix[i][j] = 0;
    if (row1) matrix[0].fill(0);
    if (col1) for (let row of matrix) row[0] = 0;
};

19 54. 螺旋矩阵

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

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

控制边界收缩

我们可以通过控制四个边的收缩来实现完整地螺旋遍历。

可以根据矩阵的行数和列数得到边界值(上、左边界位0,右、下边界分别为列数和行数-1)。我们在保证上下边界和左右边界不越界(top <= bottom && left <= right)的情况下,首先进行从左到右和从上下的遍历并储存答案,每一次遍历完都收缩一次相应边界;接下来我们判断上下边界是否越界,若没有就执行最下层从右到左的遍历,同理判断左右是否越界并执行从下到上的遍历,同时也收缩相应的边界。四个循环构成一个完整的while循环,最后输出答案即可。

边界条件判断必须包含相等的情况 相等意味着还有两边边界来到了同一行,但是此时这一行仍未遍历,所以必须要包含相等的情况。

时间复杂度:O(m×n)

空间复杂度:O(1)

js 复制代码
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function(matrix) {
    const m = matrix.length, n = matrix[0].length;

    let top = 0; 
    let bottom = m - 1;
    let left = 0;
    let right = n - 1;
    let ans = [];

    while (top <= bottom && left <= right) {
        for (let j = left; j <= right; j++) {
            ans.push(matrix[top][j]);
        }
        top++;

        for (let i = top; i <= bottom; i++) {
            ans.push(matrix[i][right]);
        }
        right--;

        if (top <= bottom) {
            for (let j = right; j >= left; j--) {
                ans.push(matrix[bottom][j])
            }
            bottom--;
        }

        if (left <= right) {
            for (let i = bottom; i >= top; i--) {
                ans.push(matrix[i][left]);
            }
            left++;
        }
    }
    return ans;
};

20 48. 旋转图像

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

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

探寻几何翻转的规律

通过几何特性我们可以发现,将一个n×n的矩阵顺时针翻转90°等价于将其进行对角线翻转+每行进行翻转。只要发现这个几何特性,这道题就会很简单,语法和数据结构并不难。

时间复杂度:O(n2)

空间复杂度:O(1)

js 复制代码
/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var rotate = function(matrix) {
    const n = matrix.length;

    for (let i = 0; i < n; i++) {
        for (let j = i; j < n; j++) {
            [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]
        }
    } 
    for (let i = 0; i < n; i++) {
        matrix[i].reverse()
        // 手动翻转:
        // let left = 0;
        // let right = n - 1;
        // while (left < right) {
        //     [matrix[i][left], matrix[i][right]] = [matrix[i][right], matrix[i][left]];
        //     left++;
        //     right--;
        // }
    }
};

21 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

利用矩阵特性:定角查找

矩阵的每行从左到右和每列从上到下都是升序排列,所以根据矩阵特性,我们可以定一个角开始查找,根据目标的大小不断进行移位,直到走完整个矩阵。可以从右上或者左下进行寻找。例如,我们定右上角寻找,如果目标比它大那就排除它右边一整行,下移一位比较;如果目标比他小那就排除下面一整列,左移一位继续比较,这样可以既不重复也不遗漏地走完整个矩阵。

**时间复杂度:O(m + n) ** 每次循环排除掉一行或者一列。最坏结果需要排除 m + n - 1 行列

空间复杂度:O(1)

js 复制代码
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function(matrix, target) {
    const m = matrix.length, n = matrix[0].length;
    let i = 0, j = n - 1;
    while (i < m && j >= 0) {
        if (matrix[i][j] === target) return true;
        if (matrix[i][j] < target) i++;
        else j--;
    }
    return false;
};
相关推荐
天天有money15 小时前
API中转站在社媒矩阵里的价值:多平台文案如何统一改写
gpt·线性代数·ai·chatgpt·矩阵
实验如有神祝女士15 小时前
不同参数规模大模型在医学翻译场景的适配差异
论文阅读·人工智能·深度学习·学习·算法·语言模型·论文笔记
windliang16 小时前
Claude Code 源码分析(三):一次模型回答如何流进 Agent
前端·算法·ai编程
Kyrie_kk17 小时前
Java--BigInteger 超大整数计算
java·算法
汽车网络安全爱好者17 小时前
Public Key Infrastructure(二)— 深入理解 X.509 证书:从 RFC 5280 到 OpenSSL 实践
运维·服务器·算法·网络安全·汽车·密码学·可信计算技术
tkevinjd18 小时前
力扣131-分割回文串
算法·leetcode·深度优先
BioRunYiXue18 小时前
技术干货 | LiP-MS全流程解析:从实验设计到数据分析
大数据·前端·javascript·人工智能·算法·数据挖掘·数据分析
Jasmine_llq19 小时前
《P11246 [GESP202409 六级] 小杨和整数拆分》
算法·数学定理版本·时间复杂度n1/2·dp 版本
湿滑路面19 小时前
VideoPipe中集成多模态大模型做视频(图片)分析
人工智能·算法·音视频
井川廊咏19 小时前
使用typedef封装函数指针新类型
算法