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
力扣例题:剑指 Offer 29. 顺时针打印矩阵
解题思路:
解题代码:
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形打印二位数组
解题思路:
代码如下:
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的最大子方阵
力扣原题:1139. 最大的以 1 为边界的正方形
题解链接:https://leetcode-cn.com/problems/largest-1-bordered-square/solution/java-si-lu-jian-dan-by-noob-22/
解题思路:
- 最大正方形
- 正方形中只要两个节点满足条件,就满足了定义。
- 第一步:计算以当前节点(红色位置)往右的最大边长,往下的最大边长。取其中的小值minOne,因为要满足正方形。
- 第二步:通过上一步计算出来的边长,找到对应正方形中的另一个节点(黄色位置),找对应的往左的最大边长,往上的最大边长,取其小值minTwo.
- 第三步:只要
minOne <= minTwo
,那么这个正方形就成立了,边长就是minOne。
- 额外记录
- left, right, down, up数组:采用4个方向数组来记录对应位置上可以向对应方向扩展的最大长度。什么意思呢?
- 比如在方向数组中:
direction{i,j}
代表的是以当前节点为起点,计算这个方向的最长的长度。 -
left{i,j}
代表的就是以当前节点(i,j)为起点,计算往左最长的一个边,就是说从当前节点开始往左数有多少个连续的1。 - 同理可以计算其他方向数组。
- 比如在方向数组中:
- left, right, down, up数组:采用4个方向数组来记录对应位置上可以向对应方向扩展的最大长度。什么意思呢?
- 举个例子:
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 子数组最大累加和
- 力扣原题链接:剑指 Offer 42. 连续子数组的最大和
- 题解链接:题解
代码如下:
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 矩阵的运算
矩阵的加法与减法
运算规则:
- 简言之,两个矩阵相加减,即它们相同位置的元素相加减!
- 注意:只有对于两个行数、列数分别相等的矩阵(即同型矩阵),加减法运算才有意义,即加减运算是可行的.
运算性质:(假设运算都是可行的)
- 满足交换律和结合律
- 交换律
A+B= B+A
- 结合律
(A+B)+C= A+(B+C)
- 交换律
矩阵与数的乘法
-
运算规则:
数入乘矩阵λ,就是将数λ乘矩阵A中的每一个元素,记为λA或Aλ.
特别地,称- A称为A=(aij)m xs的负矩阵。 -
运算性质:
满足结合律和分配律:- 结合律:
(λu)A=λ(μA); (λ+μ)A =λA+μA
- 分配律:
λ (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列元素对应相乘,再取乘积之和.
/**
* 矩阵乘法
* 矩阵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;
}