1、螺旋矩阵

题目:

  给定一个包含 m*n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例:
 输入:
  [
   [ 1, 2, 3 ],
   [ 4, 5, 6 ],
   [ 7, 8, 9 ]
  ]
 输出:
  [1, 2, 3, 6, 9, 8, 7, 4, 5]

思路:

  使用两个个指针分别指向矩阵的左上角边界和右下角边界,在创建一个指针从矩阵的左上角开始顺时针移动,每次移动到边界进行调头。当移动完一圈后缩小边界指针的范围,当边界指针交叉时结束移动。

代码实现:

    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> list = new ArrayList<>();
        // 如果长度为 0 直接结束
        if (matrix.length == 0) {
            return list;
        }
        // 左上角边界指针
        int minRow = 0, minCol = 0;
        // 右下角边界指针
        int maxRow = matrix.length - 1, maxCol = matrix[0].length - 1;
        // 移动指针
        int rowPointer = 0, colPointer = 0;
        // 边界指针交叉时,结束循环
        while (minRow <= maxRow && minCol <= maxCol) {
            // 从左往后遍历行
            while (colPointer <= maxCol) {
                list.add(matrix[rowPointer][colPointer++]);
            }
            // 指针越界后归为
            colPointer--;
            rowPointer++;
            // 从上往下编列列
            while (rowPointer <= maxRow) {
                list.add(matrix[rowPointer++][colPointer]);
            }
            rowPointer--;
            colPointer--;
            // 防止指针将输出过的 minRow和minCol再次输出
            if (minRow < maxRow && minCol < maxCol) {
                while (colPointer >= minCol) {
                    list.add(matrix[rowPointer][colPointer--]);
                }
                colPointer++;
                rowPointer--;
                while (rowPointer > minRow) {
                    list.add(matrix[rowPointer--][colPointer]);
                }
                rowPointer++;
                colPointer++;
            }
            // 缩小边界范围
            minCol++;
            maxRow--;
            minRow++;
            maxCol--;
        }
        return list;
    }

2、0 所在的行列清 0

题目:

  如果矩阵中某个元素为 0,则将其所在行和列清零。
示例:
 输入:
  [
   [ 1, 2, 3 ],
   [ 4, 0, 6 ],
   [ 7, 8, 9 ]
  ]
 输出:
   [ 1, 0, 3 ]
   [ 0, 0, 0 ]
   [ 7, 0, 9 ]

思路:

  遍历矩阵将所有含有 0 的行和列分别保存到临时数组,遍历临时数组,将矩阵中指定的行和列换为 0。

代码实现:

    public static void zero(int[][] matrix) {
        // 记录含有0的行
        int[] row = new int[matrix.length];
        // 记录含有0的列
        int[] col = new int[matrix[0].length];
        // 查看矩阵中那些行和列中含有零
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = 1;
                    col[j] = 1;
                }
            }
        }
        // 将矩阵中指定的行和列变为0
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                if (row[i] == 1 || col[j] == 1) {
                    matrix[i][j] = 0;
                }
            }
        }
        // 打印矩阵
        for (int[] ints : matrix) {
            System.out.println(Arrays.toString(ints));
        }
    }

3、Z 字型打印矩阵

题目:

  将指定矩阵按 Z 字型打印出来
示例:
 输入:
  [
   [ 1, 2, 3 ],
   [ 4, 5, 6 ],
   [ 7, 8, 9 ]
  ]
 输出:
  [1, 2, 4, 7, 5, 3, 6, 8, 9]

思路:

  创建一个指针以 Z 型遍历矩阵,指针斜着移动的方向有两个路,往右上方向移动时,当移动到上边界时往右移动一格再往左下移动,当移动到右边界时往下移动一格再往左下移动;往左下方向移动时,当移动到下边界时往右移动一格再往右上移动,当移动到左边界时往下移动一格再往右上移动。

代码实现:

    public static void typeZ(int[][] matrix) {
        // 指针
        int col = 0;
        int row = 0;
        // 记录矩阵的边界
        int colBoundary = matrix[0].length - 1;
        int rowBoundary = matrix.length - 1;
        // true为向右上走,false左下走
        boolean direction = true;
        // 如果超过边界则结束
        while (col <= colBoundary && row <= rowBoundary) {
            if (direction) {
                System.out.print(matrix[row][col] + " ");
                // 如果指针到了上边界,则往右走
                if (row == 0 && col < colBoundary) {
                    col++;
                    direction = false;
                } else if (col == colBoundary) {
                    // 如果到了右边界,则往下走,接着往左下走
                    row++;
                    direction = false;
                } else {
                    // true往右上走
                    col++;
                    row--;
                }
            } else {
                System.out.print(matrix[row][col] + " ");
                if (col == 0 && row < rowBoundary) {
                    // 到左边界往下走
                    row++;
                    direction = true;
                } else if (row == rowBoundary) {
                    // 到下边界往右走
                    col++;
                    direction = true;
                } else {
                    // true往左下走
                    col--;
                    row++;
                }
            }
        }
    }

4、边界为 1 的最大正方形

题目:

  给定一个 N*N 的矩阵,在这个矩阵中,只有 0 和 1 两种值,返回边框全是 1 的最大正方形的边长长度。

思路:

  方法一:暴力破解,从最大的阶数往最小的阶数依次遍历,扫描所有能组成指定阶数的正方形,查看正方形的边是否含有 0。

  方法二:动态规划,我们可以维护一个三维数组对矩阵做预处理,第一维是矩阵的行的个数,第二维是矩阵列的个数,第三维长度为二,分别存储该下标对应于矩阵指定位置,右边 1 的个数和下边 1 的个数(包含自己)。当判断正方形的边是否都是 1 的时候,只需要判断正方形的左上角、右上角、左下角在三维数组值是否满足条件即可,不需要再遍历正方形的每条边。

代码实现:

  方法一:暴力破解

    public static int largest1BorderedSquare1(int[][] grid) {
        // 矩阵的高
        int height = grid.length;
        // 矩阵的宽
        int wide=grid[0].length;
        // 需要求正方形的阶数
        int orderNumber = height;
        while (orderNumber > 0) {
            // 行, 当前位置加要寻找的阶数不能大于行总长度
            for (int i = 0; i <= height - orderNumber; i++) {
                col:
                // 列, 当前位置加要寻找的阶数不能大于宽总长度
                for (int j = 0;j <= wide - orderNumber ;j++) {
                    // 指针
                    int row = i;
                    int col = j;
                    // 计算对角坐标
                    int diagonalAngleRow = row + orderNumber - 1;
                    int diagonalAngleCol = col + orderNumber - 1;
                    // 检查上边是否含有0
                    while (col <= diagonalAngleCol) {
                        if (grid[row][col++] == 0) {
                            continue col;
                        }
                    }
                    // 越界后,归位
                    col = diagonalAngleCol;
                    // 检查右边是否含有0
                    while (row <= diagonalAngleRow) {
                        if (grid[row++][col] == 0) {
                            continue col;
                        }
                    }
                    // 检测下边是否含有0
                    while (diagonalAngleCol >= j) {
                        if (grid[diagonalAngleRow][diagonalAngleCol--] == 0) {
                            continue col;
                        }
                    }
                    // 越界后,归位
                    diagonalAngleCol = j;
                    // 检测左边是否含有0
                    while (diagonalAngleRow >= i) {
                        if (grid[diagonalAngleRow--][diagonalAngleCol] == 0) {
                            continue col;
                        }
                    }
                    // 返回阶数
                    return orderNumber;
                }
            }
            orderNumber--;
        }
        return 0;
    }

  方法二:动态规划

    public static int largest1BorderedSquare(int[][] grid) {
        if (grid.length == 0) {
            return 0;
        }
        int height = grid.length;
        int wide = grid[0].length;
        // 创建临时数组来对矩阵进行预处理,防止越界
        int[][][] temp = new int[height + 1][wide + 1][2];
        for (int i = height - 1; i >= 0; i--) {
            for (int j = wide - 1; j >= 0; j--) {
                // 如果当前数为1,则将其右边
                if (grid[i][j] == 1) {
                    // 右边
                    temp[i][j][0] = 1 + temp[i][j + 1][0];
                    // 下边
                    temp[i][j][1] = 1 + temp[i + 1][j][1];
                }
            }
        }
        // 要寻找的正方形的阶数
        int orderNumber = height;
        while (orderNumber > 0) {
            // 行, 当前位置加要寻找的阶数不能大于行总长度
            for (int i = 0; i <= height - orderNumber; i++) {
                // 列, 当前位置加要寻找的阶数不能大于宽总长度
                for (int j = 0; j <= wide - orderNumber; j++) {
                    // 左上角往右数1的个数要>=n
                    // 左上角往下数1的个数要>=n
                    // 左下角往右数1的个数要>=n
                    // 右上角往下数1的个数要>=n
                    if (temp[i][j][0] >= orderNumber && temp[i][j][1] >= orderNumber && temp[i + orderNumber-1][j][0] >= orderNumber && temp[i][j + orderNumber-1][1] >= orderNumber){
                        return orderNumber;
                    }
                }
            }
            orderNumber--;
        }
        return 0;
    }

5、子数组最大累加和

题目:

  给定一个数组 array,返回子数组的最大累加和。
示例:
 输入:
  array=[1, -2, 3, 5, -2, 6, -1]
 输出:
  12,因为所有子数组中[3, 5, -2, -6]可以累加出最大的和 12。

思路:

  方法一:暴力破解,使用双重循环遍历每一种可能求出最大值即可。
  方法二:动态规划,创建两个变量一个记录子数组的最大和,一个记录子数组的临时和。遍历数组,每次循环将当前数和临时和相加,如果临时和大于等于 0 则继续往后加,如果小于 0 则将临时数组清零,即重定向子数组的开头。

代码实现:

  方法一:暴力破解

    public static int maxSubArray(int[] nums) {
        // 保存最大的和
        int max = nums[0];
        for (int i = 0; i < nums.length; i++) {
            int sum = nums[i];
            if (max < sum) {
                max = sum;
            }
            for (int j = i + 1; j < nums.length; j++) {
                // 每次往后加一个数,然后和最大的比较
                sum += nums[j];
                if (max < sum) {
                    max = sum;
                }
            }

        }
        return max;
    }

  方法二:动态规划

    public static int maxSubArray(int[] nums) {
        // 临时的和
        int temp = 0;
        // 保存最大的和
        int max = nums[0];
        for (int num : nums) {
            temp += num;
            if (max < temp) {
                max = temp;
            }
            // 如果临时的和大于0则继续往下加,否则重定向开头
            if (temp < 0) {
                temp = 0;
            }
        }
        return max;
    }

6、子矩阵的最大累加和

题目:

  给定一个矩阵 matrix,其中的值有正、有负、有 0,返回子矩阵的最大累加和。

示例:
 输入:
  [
   [ -1, -1, -1],
   [ -1, 2, 2],
   [ -1, -1, -2]
  ]
 输出:
  4,其中最大累加和的子矩阵为:2 2

思路:

  我们可以把矩阵按行分割成小矩阵,然后对小矩阵每一列上的值求和,可以得到一个数组,则求子矩阵的最大累加和就转换为子数组的最大累加和。所以我们只需要遍历由不同的矩阵组成的数组,求出子数组的最大累加和即可。
代码实现:

    public static int maxMatrix(int[][] matrix) {
        if (matrix.length == 0) {
            return 0;
        }
        // 存储矩阵的高
        int maxHeight = matrix.length;
        int maxWide = matrix[0].length;
        // 起始行
        int row = 0;
        // 保存最大的和
        int max = 0;
        while (row < maxHeight) {
            int[] temp = new int[maxWide];
            for (int i = row; i < maxHeight; i++) {
                // 按列累加
                for (int j = 0; j < maxWide; j++) {
                    temp[j] += matrix[i][j];
                }
                // 累加完成后求和
                int t = maxSubArray(temp);
                // 保留最大的值
                if (max < t) {
                    max = t;
                }
            }
            row++;
        }
        return max;
    }

    public static int maxSubArray(int[] nums) {
        // 临时的和
        int temp = 0;
        // 保存最大的和
        int max = nums[0];
        for (int num : nums) {
            temp += num;
            if (max < temp) {
                max = temp;
            }
            // 如果临时的和大于0则继续往下加,否则重定向开头
            if (temp < 0) {
                temp = 0;
            }
        }
        return max;
    }

7、矩阵的四则运算

1)加法和减法

  两个矩阵相加减,即他们相同位置的元素相加减,矩阵的加法和减法满足交换律和结合律。注意:只能对于两个行数和列数都相等的矩阵进行加减法。

     [ a1, a2, a3 ]
a =  [ a4, a5, a6 ]
     [ a7, a8, a9 ]

     [ b1, b2, b3 ]
b =  [ b4, b5, b6 ]
     [ b7, b8, b9 ]

        [ a1 ± b1, a2 ± b2, a3 ± b3 ]
a ± b = [ a4 ± b4, a5 ± b5, a6 ± b6 ]
        [ a7 ± b7, a8 ± b8, a9 ± b9 ]

2)乘法和除法

  将矩阵和一个数值进行乘法和除法时,则对矩阵中的每个元素进行乘/除。当两个矩阵进行乘除法时,将 A 矩阵的第一行的 B 矩阵的每一列相乘得到结果集的第一行。乘法和除法满足分配律和结合律。注意:A 矩阵的行数必须等于 B 矩阵的列数才能进行乘/除法。

     [ a1, a2]
a =  [ a3, a4]

     [ b1, b2, b3 ]
b =  [ b4, b5, b6 ]

     [a1b1 + a2b4, a1b2 + a2b5, a1b3 + a2b6]
ab = [a3b1 + a4b4, a3b2 + a4b5, a3b3 + a4b6]

标题:多维数组和矩阵的相关算法题
作者:Yi-Xing
地址:http://47.94.239.232/articles/2019/11/24/1574587705274.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!