矩阵拆分 (暴力枚举) JAVA

题目:

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

问题描述:

小蓝有一个 100 行 100 列的矩阵,矩阵的左上角为 1 。其它每个位置正好比其左边的数大 2,比其上边的数大 1 。

例如,第 1 行第 2 列为 3,第 2 行第 2 列 为 4,第 10 行第 20列为 48。

小蓝想在矩阵中找到一个由连续的若干行、连续的若干列组成的子矩阵,使得其和为
2022,请问这个子矩阵中至少包含多少个元素(即子矩阵的行数和列数的乘积)。

答案:12


dfs + 回溯:

解题:

做这道题我是用的 dfs + 回溯的做法,思路就是从任意一点作为起点,遍历出一个矩形等于2022,就记录面积。思路与 最大子矩阵(暴力破解)思路完全一致。想要练 dfs + 回溯的话,可以用这道题试试手。

本次主要是分析下面的枚举思路的,所以直接给代码了。

dfs + 回溯代码:

import java.util.*;

public class text{
	 public static int a[][];
	 public static void main(String[] args){
		 a = new int[100][100];
		 for(int i = 0; i < 100; i ++) {
			 for(int j = 0; j < 100; j ++) {
				 if(i == 0 && j == 0) { 
					 a[i][j] = 1;
					 continue;
				 }
			     if(j == 0) {
			    	 a[i][j] = a[i - 1][j] + 1;
			    	 continue;
			     }
			     a[i][j] = a[i][j - 1] + 2; 
			 }
		 }
		 
		 for(int i = 50; i < 100; i ++) 
			 for(int j = 50; j < 100; j ++) {
				 minone = Integer.MAX_VALUE;
				 int bf = a[i][j];
				 dfs(i, j, a[i][j]);
				 a[i][j] = bf;
				 lastmin = Math.min(lastmin, minone);
				  mini = Integer.MAX_VALUE;
				  minj = Integer.MAX_VALUE;
				  maxi = Integer.MIN_VALUE;
				  maxj = Integer.MIN_VALUE;
			 }
		 System.out.println(lastmin);
} 
 public static int minone = Integer.MAX_VALUE;
 public static int lastmin = Integer.MAX_VALUE;
 public static int mini = Integer.MAX_VALUE;
 public static int minj = Integer.MAX_VALUE;
 public static int maxi = Integer.MIN_VALUE;
 public static int maxj = Integer.MIN_VALUE;
 public static void updata(int x, int y) {
	 mini = Math.min(x, mini);
	 minj = Math.min(y, minj);
	 maxi = Math.max(x, maxi);
	 maxj = Math.max(y, maxj);
 }
 public static void dfs(int i, int j, int sum) {
	 a[i][j] = 200;
	 updata(i, j);
	 if(sum == 2022 && check()) {
		 minone = Math.min(minone, (maxj - minj + 1) * (maxi - mini + 1));
		 return;
	 }
	 if(sum > 2022) return;
	 int fx[] = {-1, 1, 0, 0};
	 int fy[] = {0, 0, -1, 1};
	 int fxx = 0, fyy = 0;
	 for(int k = 0; k < 4; k ++) {
		fxx = i + fx[k];
		fyy = j + fy[k];
		int bmi = mini;
		int bmj = minj;
		int bxi = maxi;
		int bxj = maxj;
		if(check()) {
			if(fxx >= 50 && fxx <= 99 && fyy >= 50 && fyy <= 99 && a[fxx][fyy] != 200) {
				 
				int bf = a[fxx][fyy];
				dfs(fxx, fyy, sum + bf);
				a[fxx][fyy] = bf;
				 
			}
		}
		else {
			if(fxx >= mini && fxx <= maxi && fyy >= minj && fyy <= maxj && a[fxx][fyy] != 200) {
				int bfr = a[fxx][fyy];
				dfs(fxx, fyy , sum + bfr);
				a[fxx][fyy] = bfr;
			}
		}
		mini = bmi;
		minj = bmj;
		maxi = bxi;
		maxj = bxj;
	 }
	 
 }
 public static boolean check() {
	 for(int i = mini; i <= maxi; i ++)
		 for(int j = minj; j <= maxj ; j ++)
			 if(a[i][j] != 200) return false;
	 return true;
 }
}

需要注意的是上述代码,从50~100开始遍历就可以了,前面(1 ~ 50)每个点的值太小了,要20多个点甚至50多个点菜才可能构成2022,一个是太费时间,另一个就是人家题目要求的是最小值。所以答案应该在数大的后半部分。


枚举法:

解题:
这个枚举法是人家给的答案,对答案的解析也比较粗糙,但是代码量少,且时间复杂度还可以接受1000个点以内用这个方法还是很不错的。特意学了一下,以后混分用😄。

原解析:
在这里插入图片描述
由于上述,解析说的太粗糙了,我认真学习了一下代码,以下是我对这个枚举的理解:
1.首先需要知道,此枚举法的下标是从(1, 1)开始的,因为其核心公式需要借助数组(0,0)点为0这个初始值

2.需要构造题目描述的矩阵,此枚举法,采用先构造第一行再构造所有的方式

代码:

a[1][1] = 1;
      for(int i = 2 ; i <= 100 ; i ++) a[1][i] = a[1][i - 1] + 2;
      for(int i = 2;i <= 100;i++)
         for(int j = 1;j <= 100;j++)
            a[i][j] = a[i - 1][j] + 1;

3.核心1:即单独构造一个sum二维数组且sum[i][j]表示纵坐标满足x∈[0 ~ i],横坐标满足y∈[0 ~ j]内所有a[x][y]和。

例如:
在这里插入图片描述

更新sum数组的代码:

   for(int i = 1;i <= 100;i++){
         for(int j = 1;j <= 100;j++)
            sum[i][j] = sum[i][j - 1] + a[i][j];
         
         for(int j = 1;j <= 100;j++)
            sum[i][j] = sum[i][j] + sum[i - 1][j];
      }
      

即先更新本行值,再将上行的值加过来。

4.枚举两个点(需要四重for循环)一这两个点作为查询矩阵的左上角,和右下角两个点分别以 (i , j) , (k, l)表示如果符合值为2022就计算矩阵面积N = (k - i + 1) * (l - j + 1)并更新元素值。(这个不难证明画个图就知道了)
代码:

ans = Math.min(ans,(k - i + 1) * (l - j + 1));

5.核心2:还记得我们枚举的矩阵左上角为(i, j) ,右下角为(k, l)么,那么怎么结合sum数组计算出这个矩阵内所有元素值的总和呢?

以下是图解:

在这里插入图片描述

1. 注意上述我们用的a数组进行举例的

2. sum[k][l]就是整个a数组的元素值的总和,也就是整个矩阵

3. 我们目标是求出红色阴影部分所有元素值的总和,就是我们的代求子矩阵,所以我们必需用整个矩阵减去上图蓝色部分,和上图橙色部分。 但是蓝色和橙色部分被减过两次。所所以必须要再加上一个sum[i - 1][j - 1],至于为什么i,j要减1,因为AC,AD边都属于我们子矩阵部分,不能被减去。

代码:

public static int calc(int i , int j , int k , int l){
      return sum[k][l] - sum[k][j - 1] - sum[i - 1][l] + sum[i - 1][j - 1];
   }

本题枚举完整代码如下:

import java.util.*;
public class Main {
   static int [][] a = new int [150][150];//存储矩阵
   static int [][] sum = new int [150][150];
   public static int calc(int i , int j , int k , int l){
      return sum[k][l] - sum[k][j - 1] - sum[i - 1][l] + sum[i - 1][j - 1];
   }
   public static void main(String[] args) {
      a[1][1] = 1;
      for(int i = 2 ; i <= 100 ; i ++) a[1][i] = a[1][i - 1] + 2;
      for(int i = 2;i <= 100;i++)
         for(int j = 1;j <= 100;j++)
            a[i][j] = a[i - 1][j] + 1;
      for(int i = 1;i <= 100;i++){
         for(int j = 1;j <= 100;j++)
            sum[i][j] = sum[i][j - 1] + a[i][j];
         
         for(int j = 1;j <= 100;j++)
            sum[i][j] = sum[i][j] + sum[i - 1][j];
      }
      int ans = 100 * 100;
      for(int i = 1;i <= 100;i++)
         for(int j = 1;j <= 100;j++)
            for(int k = i;k <= 100;k++)
               for(int l = j;l <= 100;l++)
                  if(calc(i, j, k, l) == 2022)
                     ans = Math.min(ans,(k - i + 1) * (l - j + 1));
      System.out.println(ans);
      
   }
}


总结:

总结来所本题官方代码,思路很新颖,在特定矩阵题目的话还是很实用的。就是点数如果超过1000就就会T到爆炸,点数在1000以内还是很实用的。
至于 dfs + 回溯,依旧很强。就是特定情况下,需要的代码比较多。
混分不易😭。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值