美文网首页
二维矩阵转换(多种遍历方法)

二维矩阵转换(多种遍历方法)

作者: _code_x | 来源:发表于2021-06-05 21:35 被阅读0次

    1.螺旋矩阵(54-中)

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

    示例

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

    思路:直接模拟按层填充,定义左右上下边界,循环遍历矩阵。注意:如果是方阵,就可以直接退出循环,但是,如果存在l > r || t > b,则证明最后一次不是圈,是一行或者一列,这时我们可以直接终止循环。

    代码

    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> ans = new ArrayList<>();
        int m = matrix.length, n = matrix[0].length;
        int l = 0, r = n - 1, t = 0, b = m - 1;
        while (l <= r && t <= b) {
            for (int j = l; j <= r; ++j) {
                ans.add(matrix[t][j]);
            }
            t++;
            for (int i = t; i <= b; ++i) {
                ans.add(matrix[i][r]);
            }
            r--;
            if (l > r || t > b) {
                break;
            }
            for (int j = r; j >= l; --j) {
                ans.add(matrix[b][j]);
            }
            b--;
            for (int i = b; i >= t; --i) {
                ans.add(matrix[i][l]);
            }
            l++;
        }
        return ans;
    }
    

    2.螺旋矩阵II(59-中)

    题目描述:生成1-n^2的所有元素,且元素按顺时针螺旋排列的正方形矩阵

    示例

    输入: 3
    输出:
    [
     [ 1, 2, 3 ],
     [ 8, 9, 4 ],
     [ 7, 6, 5 ]
    ]
    

    思路:与上题思路相同,区别是退出循环机制,采用num < tar,这样避免了判断奇偶的情况。

    代码

    public int[][] generateMatrix(int n) {
        int l = 0, r = n - 1, t = 0, b = n - 1;
        int[][] matrix = new int[n][n];
        int num = 1;
        while (l <= r && t <= b) {
            for (int j = l; j <= r; ++j) {
                matrix[t][j] = num++;
            }
            t++;
            for (int i = t; i <= b; ++i) {
                matrix[i][r] = num++;
            }
            r--;
            for (int j = r; j >= l; --j) {
                matrix[b][j] = num++;
            }
            b--;
            for (int i = b; i >= t; --i) {
                matrix[i][l] = num++;
            }
            l++;
        }
        return matrix;
    }
    

    3.对角线打印矩阵(498-中)

    题目描述:给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。

    示例

    输入:
    [
     [ 1, 2, 3 ],
     [ 4, 5, 6 ],
     [ 7, 8, 9 ]
    ]
    
    输出:  [1,2,4,7,5,3,6,8,9]
    

    思路:两种方向的变更,右上到左下,依次交换方向,将结果加入数组。流程如下:

    • 确定循环次数,即方向变更的次数 m + n - 1
    • 从0开始,偶数次数为右上,奇数次数为右下(通过取余实现)
    • 最后确定两个方向具体实现,比如右上方向,设横坐标x, 纵坐标y,一般变化(x--, y++),但是注意边界处理,即上图中1 - 2, 3 - 6,对应不同的边界处理。左下方向类似。具体见代码

    代码

    class Solution {
        public int[] findDiagonalOrder(int[][] mat) {
            int rowLength = mat.length;
            int colLength = mat[0].length;
    
            int[] ans = new int[rowLength * colLength];
            int loop = rowLength + colLength - 1;
            int x = 0;
            int y = 0;
            int index = 0;
    
            for (int i = 0; i < loop; i++) {
                if (i % 2 == 0) {
                    while (x >= 0 && y < colLength) {
                        ans[index++] = mat[x][y];
                        x--;
                        y++;
                    }
                    if (y < colLength) {
                        // 对应1-2边界
                        x++;
                    } else {
                        // 对应3-6边界,ps:需要将上一步while中的x和y位置进行修正(x加1, y不变)
                        x += 2; 
                        y--;
                    }
                } else {
                    while (x < rowLength && y >= 0) {
                        ans[index++] = mat[x][y];
                        x++;
                        y--;
                    }
                    if (x < rowLength) {
                        // 对应4-7边界
                        y++;
    
                    } else {
                        // 对应8-9边界
                        x--;
                        y += 2;
                    }
                }
            }
            return ans;
        }
    }
    

    4.旋转图像(48-中)

    题目描述:给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

    必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

    示例

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

    思路

    法1:直接模拟旋转,可以看成一圈一圈的移动。注意:内层循环,为了避免奇数情况,每一圈可能有一个不移动,我们对n进行加1,可以自行举例n = 2 和 n = 3的情况。

    法2:先水平翻转,然后主对角线翻转。

    代码

    // 代码1
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n / 2; i++) {
            for (int j = 0; j < (n + 1) / 2; j++) {
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[n - j - 1][i];
                matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
                matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
                matrix[j][n - i - 1] = tmp;
            }
        }
    }
    
    // 代码2
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        // 水平翻转
        for (int i = 0; i < n / 2; i++) {
            for (int j = 0; j < n; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n - i - 1][j];
                matrix[n - i - 1][j] = temp;
            }
        }
        // 主对角线翻转
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
    }
    

    5.搜索二维矩阵(74-中)

    题目描述:编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

    • 每行中的整数从左到右按升序排列。
    • 每行的第一个整数大于前一行的最后一个整数。

    示例

    输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
    输出:true
    

    思路:本题关键解题关键是矩阵有序,我们可以从右上角或者左下角出发,缩小数据搜索范围,遍历寻找目标值。

    以右上角为例当前遍历左边的元素比当前值小,当前遍历下边的元素比当前值大。

    最优解为二分查找:本题中行尾和行首连接,也具有单调性,故可将二维矩阵转成一维矩阵去做。

    代码

    // 直接搜索,时间复杂度:O(m + n)
    public boolean searchMatrix(int[][] matrix, int target) {
        int i = 0, j = matrix[0].length - 1;
        while (i < matrix.length && j > -1) {
            if (matrix[i][j] == target) return true;
            else if (matrix[i][j] > target) {
                j--;
            } else {
                i++;
            }
        }
        return false;
    }
    
    // 二分查找,时间复杂度:O(log(mn))
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        int i = 0, j = m * n - 1;
        while (i < j) {
            int mid = i + ((j - i) >> 1);
            if (matrix[mid / n][mid % n] < target) {
                i = mid + 1;
            } else {
                j = mid;
            }
        }
        return matrix[i / n][i % n] == target;
    }
    

    6.搜索二维矩阵II(240-中)

    题目描述:在有序矩阵中寻找指定数值。

    • 每行的元素从左到右升序排列。
    • 每列的元素从上到下升序排列。

    示例

    输入: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,当前行排除,直接进入下一行。

    代码

    // 直接搜索,时间复杂度:O(m + n)
    public boolean searchMatrix(int[][] matrix, int target) {
        int i = 0, j = matrix[0].length - 1;
        while (i < matrix.length && j > -1) {
            if (matrix[i][j] == target) return true;
            else if (matrix[i][j] > target) {
                j--;
            } else {
                i++;
            }
        }
        return false;
    }
    
    // 二分查找,时间复杂度:O(mlogn)
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        for (int i = 0; i < m; ++i) {
            if (matrix[i][0] > target) {
                break;
            } 
            if (matrix[i][n - 1] < target) {
                continue;
            }
            int col = binarySearch(matrix[i], target);
            if (col != -1) {
                return true;
            }
        }
        return false;
    }
    // 二分查找目标值的索引(类似源码)
    public int binarySearch(int[] arr, int target) {
        int i = 0, j = arr.length - 1;
        while (i <= j) {
            int mid = (i + j) >>> 1;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                i = mid + 1;
            } else {
                j = mid - 1;
            }
        }
        return -1;
    }
    

    7.矩阵置零(73-中)

    题目描述:给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法(在函数输入矩阵上直接修改)。进阶:

    • 一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
    • 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
    • 你能想出一个仅使用常量空间的解决方案吗?

    示例

    输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
    输出:[[1,0,1],[0,0,0],[1,0,1]]
    

    思路:本题的关键点:如果在输入矩阵上原地修改把行列修改成了 0,那么再遍历到 0 的时候就不知道是原本数组中的 0 还是我们修改得到的 0。所有的解法都是为了解决该问题。

    方案1:遍历两遍矩阵,第一遍记录哪些行哪些列有0,第二遍置0。使用两个set进行记录,空间复杂度O(m + n)

    方案2:核心:将第一行和第一列作为标志位,为了避免是由于第一行和第一列本来就有0造成的置0,我们需要对第一行第一列单独进行判断,只需要加两个boolean类型变量进行判断即可。

    对于方案2优化,直接使用一个标志位,简化代码,但是不如两个标志位好理解。你可能会说一个不是造成了覆盖吗(原先第一个行某个位置不是0,现在是0)。

    假设该列没有0,那么第一行对应位置一定不是0,如果是0,那么可能是本身,也可能是下边0导致修改,如何区别呢?正序置0我们没有办法区别,逆序置0即使是覆盖,那么该位置最终也一定是0。

    代码

    // 空间复杂度:O(m + n)
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        Set<Integer> rowZero = new HashSet<>();
        Set<Integer> colZero = new HashSet<>();
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (matrix[i][j] == 0) {
                    rowZero.add(i);
                    colZero.add(j);
                }
            }
        }
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (rowZero.contains(i) || colZero.contains(j)) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
    
    // 标记法,空间复杂度:O(1)
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean row0Flag = false;
        boolean col0Flag = false;
        for (int j = 0; j < n; ++j) {
            if (matrix[0][j] == 0) {
                row0Flag = true;
                break;
            }
        }
        for (int i = 0; i < m; ++i) {
            if (matrix[i][0] == 0) {
                col0Flag = true;
                break;
            }
        }
        // 第一行第一列作为标志位
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                if (matrix[i][j] == 0) {
                    matrix[i][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 (row0Flag) {
            for (int j = 0; j < n; ++j) {
                matrix[0][j] = 0;
            }
        }
        if (col0Flag) {
            for (int i = 0; i < m; ++i) {
                matrix[i][0] = 0;
            }
        }
    }
    
    // 代码优化:一个标志
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean col0Flag = false;
         for (int i = 0; i < m; ++i) {
            if (matrix[i][0] == 0) col0Flag = true;
            for (int j = 1; j < n; ++j) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = m - 1; i >= 0; --i) {
            for (int j = n - 1; j >= 1; --j) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
            if (col0Flag) matrix[i][0] = 0;
        }
    }
    

    相关文章

      网友评论

          本文标题:二维矩阵转换(多种遍历方法)

          本文链接:https://www.haomeiwen.com/subject/kwcceltx.html