多维数组和矩阵程序练习

顺时针打印二维数组:

题目:

顺时针打印二维数组。

例如:将如下数组:

{{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
{13, 14, 15, 16}}

输出为:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10

思路:

可以先一圈一圈的打印。先打印外圈,再缩小边界范围循环打印内圈即可。打印的时候注意控制边界。

代码示例:

public class 顺时针打印二维数组 {

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

	public static void print(int[][] matrix) {
		
		int leftUpRow = 0, leftUpCol = 0;
		int rightDownRow = matrix.length - 1;
		int rightDownCol = matrix[0].length - 1;
		while(leftUpCol <= rightDownCol && leftUpRow <= rightDownRow) {
			int c = leftUpCol, r = leftUpRow;
			// 打印上面一条边
			while (c <= rightDownCol) {
				System.out.print(matrix[r][c++] + " ");
			}
			// 恢复
			c = rightDownCol;
			r++;
			//打印右边一条边
			while (r <= rightDownRow) {
				System.out.print(matrix[r++][c] + " ");
			}
			// 恢复
			r = rightDownRow;
			c--;
			//打印下面一条边
			while (c >= leftUpRow) {
				System.out.print(matrix[r][c--] + " ");
			}
			// 恢复
			c = leftUpRow;
			r--;
			//打印下面一条边
			while (r > leftUpRow) {
				System.out.print(matrix[r--][c] + " ");
			}
			leftUpCol++;leftUpRow++;rightDownCol--;rightDownRow--;
		}
	}
}

程序运行结果:

1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10 

0所在的行列清零:

题目:

如果矩阵中某个元素为0,则将其所在的行和列清零。

1 2 3 4

5 6 0 8

9 0 11 12

13 14 15 16

思路:

先扫描整个二维数组,开辟两个一维数组,分别标记零所在的行和列。最后,在整个二维数组中,如果发现行或者列被标记,所在的行或者列的数据就清零。

代码示例:

public class 零所在的行和列清零 {

	public static void main(String[] args) {
		int[][] matrix = {{1, 2, 3, 4},
						{5, 0, 7, 8},
						{9, 10, 0, 12},
						{13, 14, 15, 16}};
		clear(matrix);
		util.ArrayUtil.printMatrix(matrix);
		
	}

	public static void clear(int[][] matrix) {
		// 标记0所在的行
		int[] rowFlag = new int[matrix.length];
		// 标记0所在列
		int[] colFlag = 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) {
					rowFlag[i] = 1;
					colFlag[j] = 1;
				}
			}
		}
		
		for(int i = 0; i < matrix.length; i++) {
			for(int j = 0; j < matrix[0].length; j++) {
				// 如果行或者列被标记,则该行和该列的所有数据清零
				if(rowFlag[i] == 1 || colFlag[j] == 1) {
					matrix[i][j] = 0;
				}
			}
		}
	}
}

程序运行结果:

1	0	0	4	
0	0	0	0	
0	0	0	0	
13	0	0	16

Z字形打印矩阵:

题目:

按如下规律,打印出矩阵元素。

在这里插入图片描述

思路:

做该题时,可以分解为打印两个方向,从左下角打印到右上角,再从右上角打印到左下角。分别控制好边界的转化方向即可。

代码示例:

public class Z形打印矩阵 {

	public static void main(String[] args) {
		int[][] matrix = {{1, 2, 3, 4},
				  		{5, 6, 7, 8},
				  		{9, 10, 11, 12}};
		zPrint(matrix);
	}

	public static void zPrint(int[][] matrix) {
		int row = matrix.length;
		int col = matrix[0].length;
		int r = 0;
		int c = 0;
		// 打印方向,true为从左下角向右上方打印。false为从右上方像向左下方打印
		boolean direction = true;
		while(r < row && c < col) {
			if(direction) {
				// 从左下到右上的斜线
				System.out.print(matrix[r][c] + " ");
				// 在第一行,列未到边界,只能向右走
				if(r == 0 && c < col - 1) {
					c++;
					direction = false;
					continue;
				}else if(r > 0 && c == col - 1){//走到最后一列,只能向下走
					r++;
					direction = false;
					continue;
				}else { // 继续走上坡
					r--;
					c++;
				}
			}else { // 从右上角往左下角走
				System.out.print(matrix[r][c] + " ");
				// 走到第一列,行未到边界。只能往下走
				if(c == 0 && r < row - 1) {
					r++;
					direction = true;
					continue;
				}else if(r == row - 1){ // 走到最后一行,只能往右走
					direction = true;
					c++;
					continue;
				}else { // 继续走下坡路
					r++;
					c--;
				}
			}

程序运行结果:

1 2 5 9 6 3 4 7 10 11 8 12 

边界为1的最大子方阵:

题目:

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

例如:

{0, 1, 1, 1, 1},
{0, 1, 0, 0, 1},
{0, 1, 0, 0, 1},
{0, 1, 1, 1, 1},
{1, 1, 0, 1, 1}	

其中,边框全是1的最大正方形的大小是4*4,返回4.

思路:

本题为基础解法,枚举。下道题该题的优化的解法。

枚举,从n ~ 1 遍历每个正方形的四条边,如果四条边都是1,则返回n。

代码示例:

public class 边界为1的最大子方阵 {

	public static void main(String[] args) {
		int[][] matrix = {
				{0, 1, 1, 1, 1},
				{0, 1, 0, 0, 1},
				{0, 1, 0, 0, 1},
				{0, 1, 1, 1, 1},
				{1, 1, 0, 1, 1}};
		int max = solve(matrix);
		System.out.println(max);
	}

	private static int solve(int[][] matrix) {
		int N = matrix.length; 
		int n =N; //记录最大子方阵边长
		while (n > 0) { 
			for(int i = 0; i < N; i++) {
				if(i + n > N) break; 
				L3: // 标签,当在检查子方阵边长时,如果遇到边长有0,则跳出循环,重新继续执行下一行
				for(int j = 0; j < N; j++) {
					if(j + n > N) break;
					//检查四条边是否全为1
					int row = i;
					int col = j;
					//判断上边一条边
					while (col < j + n) {
						if(matrix[row][col++] == 0) {
							continue L3;
						}
					}
					col--;
					//判断右边一条边
					while (row < i + n) {
						if(matrix[row++][col] == 0) {
							continue L3;
						}
					}
					row--;
					//判断下边一条边
					while (col >= j) {
						if(matrix[row][col--] == 0) {
							continue L3;
						}
					}
					col++;
					//判断左边一条边
					while (row >= i) {
						if(matrix[row--][col] == 0) {
							continue L3;
						}
					}
					return n;
				}
			}
			n--;
		}
		return 0;
	}

}

程序运行结果:

4

边界为1的最大子方阵优化:

思路分析:

上题中的枚举算法时间复杂度为O(n^4), N * N * N * 4n的时间复杂度。

可以通过优化4n的时间复杂度,将整个解法的时间复杂度降到O(n^3).

思路:预处理方法。

​ 通过预处理方法,先生成一个对应的三维数组,二维平面空间和矩阵大小相等,第三维存储两个元素,第一个数表示,包括自己以及右方连续为1的个数,如果自己为0,则该数为0。第二个数表示,包括自己以及下方连续为1的个数,如果自己为0,则该数为0。通过生成这个辅助表,可以在判断是否是最大方阵的时候大大加快判断速度,可将4个while循环去除,每次只需判断一次即可,判断复杂度变为O(1)。

生成辅助举证举例如下:矩阵为记为A

{0, 1, 1, 1, 1},
{0, 1, 0, 0, 1},
{0, 1, 0, 0, 1},
{0, 1, 1, 1, 1},
{1, 1, 0, 1, 1}	

如以上矩阵可以生成如下辅助数组help:

{{00}, {44}, {3, 1}, {2, 1}, {1, 5}},
{{00}, {14}, {0, 0}, {0, 0}, {1, 4}},
{{00}, {13}, {0, 0}, {0, 0}, {1, 3}},
{{00}, {4, 2}, {3, 1}, {2, 2}, {1, 2}},
{{21}, {11}, {0, 0}, {2, 1}, {1, 1}}
//注意:生成辅助数组时,因为每个点记录的都是右下方的统计值,所以
//     必须从右下方开始倒退生成整个辅助数组。

当生成如上辅助数组时,每次检查四条边的时候,可以直接检查三个顶点的数字,即可判断是否四条边都是1。例如:当检查n=4时,检查到 A[0][1] 点时,可以直接判断(help[0][1][0] >= n &&help[0][1][1] >= n help[0][1+n][1] >= n help[0+n][1][0] >= n)如果都成立,即可知四条边上的值为1。此时直接返回n即可。具体代码示例如下:

代码示例:

public class 边界为1的最大子方阵_优化 {
	
	static int[][][] help; //声明全局变量,辅助数组
	public static void main(String[] args) {
		int[][] matrix = {
				{0, 1, 1, 1, 1},
				{0, 1, 0, 0, 1},
				{0, 1, 0, 0, 1},
				{0, 1, 1, 1, 1},
				{1, 1, 0, 1, 1}};
		generateHelp(matrix);
		System.out.println("生成的辅助数组数据如下:");
		System.out.println("-------------------------------------");
		printHelp(help, matrix.length);
		System.out.println("-------------------------------------");
		int max = solve(matrix);
		System.out.println("最大子方阵的边长为:" + max);
	}

	// 打印生成辅助的数组数据
	private static void printHelp(int[][][] help, int N) {
		for(int i = 0; i < N; i++) {
			for(int j = 0; j < N; j++) {
				System.out.print(help[i][j][0] + "," + help[i][j][1] + "\t");
			}
			System.out.println();
		}
	}

	// 生成辅助数组的方法
	private static void generateHelp(int[][] matrix) {
		int N = matrix.length;
		help = new int[N][N][2];
		int lastRow = N - 1; 
		// 先初始化最后一行辅助数据。用于生成其他数据
		for(int i = N - 1; i >= 0; i--) {
			int value = matrix[lastRow][i];
			if(value == 1) {
				if(i == N - 1) {
					// 当初始化最后一列时,如果value为1,则右边为1
					help[lastRow][i][0] = 1;
				}else {
					// 如果非最后一列,则右边的初始化为:从自身到右边的连续1的个数
					help[lastRow][i][0] = help[lastRow][i + 1][0] + 1;
				}
				// 当value=1时,下方1的个数初始化为1
				help[lastRow][i][1] = 1;
			}
		}
		// 生成整个辅助数组的数据
		for(int i = N - 2; i >= 0; i--) {
			for(int j = N - 1; j >= 0; j--) {
				int value = matrix[i][j];
				if(value == 1) {
					if(j == N - 1) {
						help[i][j][0] = 1;
					}else {
						// 当value=1时,右方连续1的个数
						help[i][j][0] = help[i][j+1][0] + 1;
					}
					// 当value=1时,下方连续1的个数
					help[i][j][1] = help[i+1][j][1] + 1;
				}
			}
		}
	}

	private static int solve(int[][] matrix) {
		int N = matrix.length; 
		int n =N; //记录最大子方阵边长
		while (n > 0) { 
			for(int i = 0; i < N; i++) {
				if(i + n > N) break; 
				for(int j = 0; j < N; j++) {
					if(j + n > N) break;
					if(check(i, j, n))
						return n;
				}
			}
			n--;
		}
		return 0;
	}

	//检查以matrix[i][j]为左顶点的,边长为n的方阵的边上的数是否都为1
	private static boolean check(int i, int j, int n) {
		// 左上角那个点往右数的1的数目要 >= n
		// 左上角那个点往下数的1的数目要 >= n
		// 右上角那个点往下数的1的数目要 >= n
		// 左下角那个点往右数的1的数目要 >= n
		if(help[i][j][0] >= n && help[i][j][1] >= n 
			&& help[i+n-1][j][0] >= n && help[i][j+n-1][1] >= n)
			return true;
		return false;
	}

}

程序运行结果:

生成的辅助数组数据如下:
-------------------------------------
0,0	4,5	3,1	2,1	1,5	
0,0	1,4	0,0	0,0	1,4	
0,0	1,3	0,0	0,0	1,3	
0,0	4,2	3,1	2,2	1,2	
2,1	1,1	0,0	2,1	1,1	
-------------------------------------
最大子方阵的边长为:4

子数组最大和累加:

题目:

给定一个数组arr,返回子数组的最大累加和。

例如:arr[1, -2, 3, 5, -2, 6, -1];所有的子数组中[3, 5, -2, 6]可以累加出最大和12,所以返回12;

思路:

有两种解法:

  • 方法1 : 暴力法

    可以通过暴力法,把所有可能的子数组情况进行累加。最后比较,得出最大值即可。

    时间复杂度为:O(n^2);

  • 方法2:递推法:

    可以通过扫描一次数组,挨着累加,当累加和出现负数,直接舍弃,证明该段子数量最最大子序列没有贡献。每次累加后,与最大的数比较,如果比最大数大,就替换。详细代码如下:

    时间复杂度为:O(n)

代码示例:

public class MaxSubArray {

	public static void main(String[] args) {
		//获取10000 个 -100 - 100 的随机整数
		int[] arr = util.ArrayUtil.getRandomArr(10000, -100, 100);
		util.ArrayUtil.printArr(arr);
		long begin = System.currentTimeMillis();
		System.out.println("子数组最大累加和为:" + maxSum(arr));
		System.out.print("暴力法:");
		util.CountTime.countTime(begin);
		long begin1 = System.currentTimeMillis();
		System.out.println("子数组最大累加和为:" + maxSum1(arr));
		System.out.print("递推法:");
		util.CountTime.countTime(begin1);

	}
	
	//暴力法
	public static int maxSum(int[] arr) {
        if(arr.length == 0) return 0;
		int maxSum = arr[0]; //用来记录最大的和
		for(int i = 0; i < arr.length; i++) {
			int sum = 0; 
			for(int j = i; j < arr.length; j++) {
				sum += arr[j];
				if(sum > maxSum) {
					maxSum = sum;
				}
			}
		}
		return maxSum;
	}

	// 递推法 时间复杂度O(n)
	public static int maxSum1(int[] arr) {
        if(arr.length == 0) return 0;
		int maxSum = arr[0];
		int sum = 0;
		// left,right用来记录最大子序列和的开始和结束下标
		int left = 0, right = 0;
		for(int i = 0; i < arr.length; i++) {
			if(sum >= 0) { //左子表的最大和为正,继续向后累加
				sum += arr[i];
			}else { //为负,则舍弃,从下一个数重新开始累加。
				sum = arr[i];
				left = i; //记录左开始累加的左下标
			}
			
			if(sum > maxSum) { //如果每次累加的和大于最大记录,则替换
				maxSum = sum;
				right = i; //替换后此时,记录最大子序列的右下标
			}
		}
		System.out.println("最大子序列和的开始下标和结束下标为:" + left + "->" + right);
		return maxSum;
	}
}

程序执行结果:

31 78 93 99 -64 78 41 35 …… 
子数组最大累加和为:4253
暴力法:程序执行时间:54ms
最大子序列和的开始下标和结束下标为:8995->3811
子数组最大累加和为:4253
递推法:程序执行时间:0ms

子矩阵最大累加和:

题目:

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

例如:matrix 为:

-1 -1 -1
-1  2  2
-1 -1 -1

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

思路:

有两种方案:

  • 方案1:暴力法,求出所有可能性。不推荐。

  • 方案2:递推法,同上题。

    每行为单位,求出单行的最大子序列和。将多行的值累加为一行的值进行累加。最后求出最大值。

代码示例:

public class MaxSubMatrix {

	public static void main(String[] args) {
		int[][] matrix = {{-1, -1, -1},
						   {-1, 2, 2},
						   {-1, -1, -1}};
		System.out.println(maxSubMatrixSum(matrix));
	}

	private static int maxSubMatrixSum(int[][] matrix) {
		if(matrix.length == 0) return 0;
		final int row = matrix.length;
		final int col = matrix[0].length;
		int maxSum = 0; //历史最大的子矩阵和
		int beginRow = 0;//以它为起始行
		while(beginRow < row) {
			//按列求和
			int[] sums = new int[col]; 
			for(int i = beginRow; i < row; i++) {
				//按列累加
				for(int j = 0; j < col; j++) {
					sums[j] += matrix[i][j];
				}
				//累加完成 该方法头上题解法2
				int sum = maxSubArraySum(sums);
				if(sum > maxSum) {
					maxSum = sum;
				}
			}
			beginRow++;
		}
		return maxSum;
	}

	private static int maxSubArraySum(int[] arr) {
		if(arr.length == 0) return 0;
		int maxSum = arr[0];
		int sum = 0;
		for(int i = 0; i < arr.length; i++) {
			if(sum >= 0) { //左子表的最大和为正,继续向后累加
				sum += arr[i];
			}else { //为负,则舍弃,从下一个数重新开始累加。
				sum = arr[i];
			}
			
			if(sum > maxSum) { //如果每次累加的和大于最大记录,则替换
				maxSum = sum;
			}
		}
		return maxSum;
	}
}

程序运行结果:

4
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页