4.1 数组与矩阵

4.1.1 顺时针打印二维数组

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 1:

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

输入: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]

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

蓝桥杯《算法很美》第4章:数组与矩阵_顺时针

力扣例题剑指 Offer 29. 顺时针打印矩阵

解题思路

蓝桥杯《算法很美》第4章:数组与矩阵_顺时针_02

解题代码

public class Test24 {
	public static void main(String[] args) {
		int[][] arr = { 
				{ 1, 2, 3, 4 }, 
				{ 5, 6, 7, 8 }, 
				{ 9, 10, 11, 12 }, 
				{ 13, 14, 15, 16 } 
		};
		
		int[][] arr2 = { 
				{ 1, 2, 3}, 
				{ 4, 5, 6 }, 
				{ 7, 8, 9 }
		};
		
		// 蛇形打印二维数组:1 2 3 4 8 7 6 5 9 10 11 12 16 15 14 13 
		print1(arr);
		System.out.println();
		// 顺时针打印二维数组:
		print2(arr2);
	}
	
	/**
	 * 蛇形打印二维数组
	 * @param arr
	 */
	static void print1(int[][] arr) {
		// 总行数
		int row = arr.length;
		// 总列数
		int col = arr[0].length;
		// 每行的指针下标
		int colIndex = -1;
		for (int i = 0; i < row; i++) {
			if (colIndex>=-1&&colIndex<col) {
				colIndex++;
				while (colIndex<col) {
					System.out.print(arr[i][colIndex]+" ");
					colIndex++;
				}
				continue;
			}
			if (colIndex>=col) {
				colIndex--;
				while (colIndex>=0) {
					System.out.print(arr[i][colIndex]+" ");
					colIndex--;
				}
				continue;
			}
		}
	}
	
	/**
	 * 顺时针打印二维数组
	 * @param arr
	 */
	static void print2(int[][] arr) {
		if(arr.length == 0) {
			return;
		}
		
		int leftUpRow = 0,leftUpCol = 0;// 左上坐标
		int rightDownRow = arr.length -1,rightDownCol = arr[0].length - 1;// 右下坐标
		
		// 由外向里一圈一圈的按照 上 -> 右 -> 下 -> 左 的顺序循环打印,直到左上角和右下角坐标交错则退出循环
		// 即,退出循环条件:leftUpRow > rightDownRow || leftUpCol > rightDownCol;
		while(leftUpRow<=rightDownRow&&leftUpCol<=rightDownCol) {
			int col = leftUpRow;
			int row = leftUpCol;
			// 1.打印上边一行
			while(col <= rightDownCol) {
				System.out.print(arr[row][col++]+" ");// 上
			}
			col = rightDownCol;// 恢复一下col的最大值,防止越界
			row++;
			
			// 2.打印右边一列
			while(row <= rightDownRow) {
				System.out.print(arr[row++][col]+" ");// 右
			}
			row = rightDownRow;// 恢复一下row的最大值,防止越界
			col--;
			
			// 3.打印下边一行
			while(col >= leftUpCol) {
				System.out.print(arr[row][col--]+" ");// 下
			}
			col = leftUpCol;// 恢复一下col的最小值,防止越界
			row--;
			
			// 4.打印左边一列
			while(row > leftUpRow) {// row 和 leftUpRow 不能相等,保证不重复打印每圈左上角的数
				System.out.print(arr[row--][col]+" ");// 左
			}
			//row = leftUpRow;// 恢复一下row的最小值,防止越界
			
			// 打印向内一圈,左上++,右下--
			leftUpRow++;
			leftUpCol++;
			rightDownRow--;
			rightDownCol--;
		}
	}
}

力扣该题答案

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if (matrix.length == 0)
			return new int[] {};
		int[] result = new int[matrix.length * matrix[0].length];
		int index = 0;

		int leftUpRow = 0, leftUpCol = 0;// 左上坐标
		int rightDownRow = matrix.length - 1, rightDownCol = matrix[0].length - 1;// 右下坐标

		// 由外向里一圈一圈的按照 上 -> 右 -> 下 -> 左 的顺序循环打印,直到左上角和右下角坐标交错则退出循环
		// 即,退出循环条件:leftUpRow > rightDownRow || leftUpCol > rightDownCol;
		while (leftUpRow <= rightDownRow && leftUpCol <= rightDownCol) {
			int col = leftUpRow;
			int row = leftUpCol;
			// 1.打印上边一行
			while (index < result.length && col <= rightDownCol) {
				result[index++] = matrix[row][col++];// 上
			}
			col = rightDownCol;// 恢复一下col的最大值,防止越界
			row++;

			// 2.打印右边一列
			while (index < result.length && row <= rightDownRow) {
				result[index++] = matrix[row++][col];// 右
			}
			row = rightDownRow;// 恢复一下row的最大值,防止越界
			col--;

			// 3.打印下边一行
			while (index < result.length && col >= leftUpCol) {
				result[index++] = matrix[row][col--];// 下
			}
			col = leftUpCol;// 恢复一下col的最小值,防止越界
			row--;

			// 4.打印左边一列
            // row 和 leftUpRow 不能相等,保证不重复打印每圈左上角的数
			while (index < result.length && row > leftUpRow) {
				result[index++] = matrix[row--][col];// 左
			}
			// row = leftUpRow;// 恢复一下row的最小值,防止越界

			// 打印向内一圈,左上++,右下--
			leftUpRow++;
			leftUpCol++;
			rightDownRow--;
			rightDownCol--;
		}
		return result;
    }
}

4.1.2 0所在的行列清零

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

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

代码如下

public static void setZeroes(int[][] matrix) {
    if(matrix.length==0){
        return;
    }
    int row=matrix.length;
    int col=matrix[0].length;
    // 标记需要清0的行的数组,需要清0则标记为1,默认是0
    int[] rowMark=new int[row];
    // 标记需要清0的列的数组,需要清0则标记为1,默认是0
    int[] colMark=new int[col];
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            if(matrix[i][j]==0){
                rowMark[i]=1;
                colMark[j]=1;
            }
        }
    }
    // 需要清0的行,所有行元素设置为0
    for (int i = 0; i <row ; i++) {
        if(rowMark[i]==1){
            for (int j = 0; j < col; j++) {
                matrix[i][j]=0;
            }
        }
    }
    // // 需要清0的列,所有列元素设置为0
    for (int i = 0; i <col ; i++) {
        if(colMark[i]==1){
            for (int j = 0; j < row; j++) {
                matrix[j][i]=0;
            }
        }
    }
}

4.1.3 z形打印二位数组

蓝桥杯《算法很美》第4章:数组与矩阵_i++_03

解题思路

蓝桥杯《算法很美》第4章:数组与矩阵_i++_04

代码如下

public class Test56 {
	public static void main(String[] args) {
		int[][] arr = { 
				{ 1, 2, 3, 4 }, 
				{ 5, 6, 7, 8 }, 
				{ 9, 10, 11, 12 }, 
				{ 13, 14, 15, 16 } };

		print(arr);// 1 2 5 9 6 3 4 7 10 13 14 11 8 12 15 16 
	}

	/**
	 * Z形打印矩阵
	 * 
	 * @param arr
	 */
	static void print(int[][] arr) {
		int row = 0, col = 0;
		int maxRow = arr.length - 1, maxCol = arr[0].length - 1;

		boolean direct = true;// true: 箭头走上坡,false:箭头走下坡

		while (row <= maxRow && col <= maxCol) {
			if (direct) {// true: 箭头走上坡
				System.out.print(arr[row][col] + " ");
				if (row > 0 && col == maxCol) {// 竖向箭头向下
					direct = !direct;// 非横向箭头改为下坡方向
					row++;
					continue;
				} else if (row == 0 && col < maxCol) {// 横向箭头向右
					direct = !direct;// 非横向箭头改为下坡方向
					col++;
					continue;
				} else {// 非横竖向箭头走上坡方向
					row--;
					col++;
				}
			} else {// false:箭头走下坡
				System.out.print(arr[row][col] + " ");
				if (row == maxRow && col < maxCol) {// 横向箭头向右
					direct = !direct;// 非横向箭头改为上坡方向
					col++;
					continue;
				} else if (row < maxRow && col == 0) {// 竖向箭头向右
					direct = !direct;// 非横向箭头改为上坡方向
					row++;
					continue;
				} else {// 非横竖向箭头走下坡方向
					row++;
					col--;
				}
			}
		}
	}
}

4.1.4 边界为1的最大子方阵

蓝桥杯《算法很美》第4章:数组与矩阵_顺时针_05

力扣原题1139. 最大的以 1 为边界的正方形

题解链接https://leetcode-cn.com/problems/largest-1-bordered-square/solution/java-si-lu-jian-dan-by-noob-22/

解题思路

蓝桥杯《算法很美》第4章:数组与矩阵_数组_06

  • 最大正方形
    • 正方形中只要两个节点满足条件,就满足了定义。
    • 第一步:计算以当前节点(红色位置)往右的最大边长,往下的最大边长。取其中的小值minOne,因为要满足正方形。
    • 第二步:通过上一步计算出来的边长,找到对应正方形中的另一个节点(黄色位置),找对应的往左的最大边长,往上的最大边长,取其小值minTwo.
    • 第三步:只要minOne <= minTwo,那么这个正方形就成立了,边长就是minOne。
  • 额外记录
    • left, right, down, up数组:采用4个方向数组来记录对应位置上可以向对应方向扩展的最大长度。什么意思呢?
      • 比如在方向数组中:direction{i,j} 代表的是以当前节点为起点,计算这个方向的最长的长度。
      • left{i,j}代表的就是以当前节点(i,j)为起点,计算往左最长的一个边,就是说从当前节点开始往左数有多少个连续的1。
      • 同理可以计算其他方向数组。
  • 举个例子:

grid 数组

1 1 1
1 0 1
1 1 1

left 数组

1 2 3
1 0 1
1 2 3
grid = [[1,1,1],
        [1,0,1],
        [1,1,1]];

left[0][0] = 1; //因为从(0,0)往左找1,只能找到他自己,所以结果为1
left[0][1] = 2; //因为从 (0,0) 往左找1,能找到包含自己在内的2个1,所以结果为2
left[0][2] = 3; //因为从(0,2)往左找1,能找到包含自己在内的3个1,所以结果为3.

up 数组

1 1 1
2 0 2
3 1 3

right 数组

1 1 1
1 0 1
1 1 1

down 数组

3 1 3
2 0 2
1 1 1
  • 计算方向数组

    • 这个部分可以说是用了最简单的动态规划(这部分看代码更加容易理解)

    • up{i,j }的值,取决于grid{i,j}up{i-1,j}

    • down{i,j}的值,取决于grid{i,j}down{i+1, j}

    • 同理left和right

if (grid[i][j] == 0){
up[i][j] =0;
}else{
up[i][j] = 1 + up[i-1][j];
}


*  **校验逻辑**

```java
for ( int i = 0; i < length; i++){
 for (int j = 0; j < width; j++){
     /**
      * 1. 第一个维度的判断:
      * 从右下维度找以当前节点(i,j) 往右下扩展的最大边长minOne,
      * 这个最大值其实是 下面两个的最小值:
      *    以当前节点为起点,向右最多能扩展的长度,right[i][j]
      *    以当前节点为起点,向当前节点的下面最多能扩展的长度 down[i][j]
      */
     int minOne = Integer.min(right[i][j], down[i][j]);
     for(;minOne>0;minOne--){
         /** 
         * 2. 第二个维度的判断:
         * 找到(i,j)对应的节点(i+minOne-1,j+minOne-1)
         * 从这个节点判断其左上维度,往左上扩展的最大边长minTwo,
         * 这个minTwo同理也是left和up中较小的值。
         */
         int minTwo = Integer.min(left[i+minOne-1][j+minOne-1], up[i+minOne-1][j+minOne-1]);
         if (minOne <= minTwo){
             maxResult = Integer.max(maxResult, minOne);
             break;
         }
     }
     //如果剩下的节点已经没有可能构造出比现有结果更大的正方形格子,直接返回
     if ((i + maxResult) >= length  && j + maxResult >= width)
         return maxResult * maxResult;
 }
}

代码如下

class Solution {
    public int largest1BorderedSquare(int[][] grid) {
		int result = 0;// 返回结果

		int rows = grid.length;// 总行数
		int cols = grid[0].length;// 总列数

		int[][] left = new int[rows][cols];
		int[][] up = new int[rows][cols];
		int[][] right = new int[rows][cols];
		int[][] down = new int[rows][cols];

		// 下面开始初始化4个数组
		// 左上
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				if (grid[i][j] == 1) {
					left[i][j] = j > 0 ? left[i][j - 1] + 1 : 1;
					up[i][j] = i > 0 ? up[i - 1][j] + 1 : 1;
				}
			}
		}
		// 右下
		for (int i = rows - 1; i >= 0; i--) {
			for (int j = cols - 1; j >= 0; j--) {
				if (grid[i][j] == 1) {
					right[i][j] = j < cols - 1 ? right[i][j + 1] + 1 : 1;
					down[i][j] = i < rows - 1 ? down[i + 1][j] + 1 : 1;
				}
			}
		}

		// 下面计算结果
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				int minOne = Integer.min(right[i][j], down[i][j]);
				for (; minOne > 0; minOne--) {
					int minTwo = Integer.min(left[i + minOne - 1][j + minOne - 1], up[i + minOne - 1][j + minOne - 1]);
					if (minOne <= minTwo) {
						result = Integer.max(result, minOne);
						break;
					}
				}
			}
		}
		return result * result;
	}
}

4.1.5 子数组最大累加和

代码如下

class Solution {
    public int maxSubArray(int[] nums) {
        int result = nums[0];
        for(int i = 1;i < nums.length;i++){
           nums[i] = nums[i] + Integer.max(nums[i - 1],0);
           result = Integer.max(result,nums[i]);
        }
        return result;
    }
}

4.1.6 子矩阵最大累加和

  • 给定一-个矩阵matrix ,其中的值有正、有负、有0 ,返回子矩阵的最大累加和。
  • 例如, matrix为:
-1 -1 -1
-1 2 2
-1 -1 -1

其中最大累加和的子矩阵为: 2 2 ,所以返回4。

代码如下:

// N^3时间复杂度
private static int maxSum(int[][] matrix) {
  int beginRow = 0;// 以它为起始行

  final int M = matrix.length;
  final int N = matrix[0].length;

  int[] sums = new int[N];// 按列求和

  int max = 0;// 历史最大的子矩阵和

  while (beginRow < M) { // 起始行
    for (int i = beginRow; i < M; i++) {// 从起始行到第i行
      // 按列累加
      for (int j = 0; j < N; j++) {
        sums[j] += matrix[i][j];
      }
      //  累加完成
      //  求出sums的最大和子数组O(n)
      int t = Case05_MaxSubArray.findByDp(sums);
      if (t > max)
        max = t;
    }
    // 另起一行作为起始行.把sums清零
    Arrays.fill(sums, 0);// 快速地将sums的每个元素都设定为0
    beginRow++;
  }
  return max;
}

4.2 矩阵的运算

矩阵的加法与减法

运算规则

蓝桥杯《算法很美》第4章:数组与矩阵_顺时针_07

  • 简言之,两个矩阵相加减,即它们相同位置的元素相加减!
  • 注意:只有对于两个行数、列数分别相等的矩阵(即同型矩阵),加减法运算才有意义,即加减运算是可行的.

运算性质:(假设运算都是可行的)

  • 满足交换律和结合律
    • 交换律 A+B= B+A
    • 结合律(A+B)+C= A+(B+C)

矩阵与数的乘法

  1. 运算规则
    数入乘矩阵λ,就是将数λ乘矩阵A中的每一个元素,记为λA.
    特别地,称- A称为A=(aij)m xs的负矩阵。
  2. 运算性质
    满足结合律和分配律:
    1. 结合律: (λu)A=λ(μA); (λ+μ)A =λA+μA
    2. 分配律: λ (A+B)=λA+λB

矩阵与矩阵的乘法

运算规则:

设A=(aij)mxs,B=(bij) sxn,则A与B的乘积C =AB是这样一一个矩阵 : .
(1)行数与(左矩阵) A相同,列数与(右矩阵) B相同,即C =(cij)mxn.
(2) C的第i行第j列的元素Cq由A的第i行元素与B的第j列元素对应相乘,再取乘积之和.

蓝桥杯《算法很美》第4章:数组与矩阵_其他_08

蓝桥杯《算法很美》第4章:数组与矩阵_其他_09

蓝桥杯《算法很美》第4章:数组与矩阵_其他_10

/**
 *   矩阵乘法
 *   矩阵1为n*m矩阵,矩阵2为m*p矩阵
 *   结果为n*p矩阵
 */
public static long[][] matrixMultiply(long[][] m1, long[][] m2) {
  final int n = m1.length;
  final int m = m1[0].length;
  if (m != m2.length) throw new IllegalArgumentException();
  final int p = m2[0].length;

  long[][] result = new long[n][p];// 新矩阵的行数为m1的行数,列数为m2的列数

  for (int i = 0; i < n; i++) {//m1的每一行
    for (int j = 0; j < p; j++) {//m2的每一列
      for (int k = 0; k < m; k++) {
        result[i][j] += m1[i][k] * m2[k][j];
      }
    }
  }
  return result;
}