2017年第八届蓝桥杯JavaB组省赛 刷题笔记、思路及答案

前言

本人是二本院校大二的计算机系学生,已经报名了下一届的蓝桥杯省赛,整个寒假在家(这次的寒假挺久的哈哈)在b站学习了一些算法(现在会bfs走迷宫、dfs相关算法、递归回溯、常见排列算法),但是还是有很多算法都还不太熟悉,做起题来真是费劲。

这是我第二次完整的刷真题,第一次刷的是第七届的真题,其刷题笔记、思路和答案在:
https://blog.csdn.net/cxrshiz7890/article/details/104389137

不得不说,这次刷的第八届题目真是有点难度,开头几道题就难倒我了,花了好长时间想……(我想,考试时如果这样耗着就挂了,所以遇到不会的一定要跳过!)

不过,我相信通过思考总结后,一定会有所收获的!

刷题笔记

注:已完整解答的题目将会标记 (已完成) ,未完全解答出来的题目将在后续更新。代码设计题的代码不代表官方正确解法,只是通过了题目所给的测试而已,因此代码有误还请各位大佬指出哈哈。题目将保留原真题的完整描述,下次复习的时候可以强迫自己多读题(读懂题是关键啊!后面有道题读了十几分钟却不知道题目在讲什么,太浪费时间了!)

第一题、购物单(已完成)

【题目】:

小明刚刚找到工作,老板人很好,只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦,但又不好推辞。

这不,XX大促销又来了!老板夫人开出了长长的购物单,都是有打折优惠的。
小明也有个怪癖,不到万不得已,从不刷卡,直接现金搞定。

现在小明很心烦,请你帮他计算一下,需要从取款机上取多少现金,才能搞定这次购物。

取款机只能提供100元面额的纸币。小明想尽可能少取些现金,够用就行了。
你的任务是计算出,小明最少需要取多少现金。

以下是让人头疼的购物单,为了保护隐私,物品名称被隐藏了。

****     180.90       88****      10.25       65****      56.14        9****     104.65        9****     100.30       88****     297.15        半价
****      26.75       65****     130.62        半价
****     240.28       58****     270.62        8****     115.87       88****     247.34       95****      73.21        9****     101.00        半价
****      79.54        半价
****     278.44        7****     199.26        半价
****      12.97        9****     166.30       78****     125.50       58****      84.98        9****     113.35       68****     166.57        半价
****      42.56        9****      81.90       95****     131.78        8****     255.89       78****     109.17        9****     146.69       68****     139.33       65****     141.16       78****     154.74        8****      59.42        8****      85.44       68****     293.70       88****     261.79       65****      11.30       88****     268.27       58****     128.29       88****     251.03        8****     208.39       75****     128.88       75****      62.06        9****     225.87       75****      12.89       75****      34.28       75****      62.16       58****     129.12        半价
****     218.37        半价
****     289.69        8折

需要说明的是,88折指的是按标价的88%计算,而8折是按80%计算,余者类推。
特别地,半价是按50%计算。

请提交小明要从取款机上提取的金额,单位是元。
答案是一个整数,类似4300的样子,结尾必然是00,不要填写任何多余的内容。

【思路】:

很简单的数学题,逐个相乘然后求和即可。

但是……我还真的那么笨的一个个去算了,写了可能有15分钟吧,还好答案对了,不然到时候考试这么写,真的亏死了。

【正确答案】:

5200

扫描二维码关注公众号,回复: 11152619 查看本文章

巧妙做法:参考了网上大佬的做法,其实只需要把数据复制到一个空文本里,然后把*****用替换功能全部换成“+”号,把“半价”替换成 “ *0.5 ” (数字前面有个乘号),然后依次把xx折替换成相应的 “ *0.xx ”(前面有个乘号),最后把这段数据放到编译器里执行就好了。

第二题、纸牌三角形(已完成)

【题目】:

A,2,3,4,5,6,7,8,99张纸牌排成一个正三角形(A按1计算)。要求每个边的和相等。
下图就是一种排法(如有对齐问题,参看p1.png)。

      A
     9 6
    4   8
   3 7 5 2

这样的排法可能会有很多。

如果考虑旋转、镜像后相同的算同一种,一共有多少种不同的排法呢?

请你计算并提交该数字。

注意:需要提交的是一个整数,不要提交任何多余内容。

【思路】:

把三角形展开来看成数组,然后用全排列把数组所有可能列举出来,然后判断有多少种情况符合即可。

这道题,我居然做了1个多小时,还做错了……我把它复杂化了,我分别写了3个方法来判断是否符合边长相等、是否是旋转的情况、是否是镜像的情况。这里记录一下错误的代码……下次复习的时候得长长记性……

package _8s_LanQiao;

import java.util.ArrayList;
import java.util.List;

public class A2
{
	static List<String> list = new ArrayList<String>();
	static int count = 0;
	
	//全排列
	public static void all(int[] a, int step)
	{
		if(step == a.length - 1)
		{
			if(checkE(a))	//如果符合边长和一样
			{
				StringBuffer sb = new StringBuffer();
				for(int i = 0; i < a.length; i++)
				{
					sb.append(a[i]);
				}
				if(!checkR(a) && !checkM(a))	//如果这种情况不是镜像也不是旋转
				{
					list.add(sb.toString());
					count++;
				}
			}
		}
		
		for(int i = step; i < a.length; i++)
		{
			int temp = a[i];
			a[i] = a[step];
			a[step] = temp;
			
			all(a, step + 1);
			
			temp = a[i];
			a[i] = a[step];
			a[step] = temp;
		}
	}
	
	//检查旋转
	public static boolean checkR(int[] a)
	{
		int[] ex = new int[a.length*2];
		StringBuffer sb = new StringBuffer();
		for(int i = 0; i < a.length; i++)
			sb.append(a[i]);
		String now = sb.toString();
		
		for(int s = 0; s < list.size(); s++)
		{
			StringBuffer sb2 = new StringBuffer(list.get(s) + list.get(s));
			String getstr = sb2.toString();
			
			if(getstr.indexOf(now) != -1)	//匹配到了
				return true;	//这个是旋转的,不符合条件
		}
		return false;
	}
	
	//检查镜像
	public static boolean checkM(int[] a)
	{
		StringBuffer sb = new StringBuffer();
		for(int i = 1; i < a.length; i++)
			sb.append(a[i]);

		String now = a[0] + sb.reverse().toString();
		
		for(int i = 0; i < list.size(); i++)
		{
			String getstr = list.get(i);
			if(getstr.equals(now))	//匹配到了
				return true;	//说明是镜像,不符合条件
		}
		return false;
	}
	
	//检查是否边长和一样
	public static boolean checkE(int[] a)
	{
		int sum = a[0] + a[1] + a[2] + a[3];
		int sum2 = a[3] + a[4] + a[5] + a[6];
		int sum3 = a[6] + a[7] + a[8] + a[0];
		
		if(sum == sum2 && sum2 == sum3)
			return true;	//边长一样,符合条件
		else
			return false;
	}
	
	public static void main(String[] args)
	{
		int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9,};
		all(a, 0);
		System.out.println(count);	//得出错误答案 288
	}
}

除非思路很清晰,否则不要写那么长的代码!!!不要写那么长的代码!!不要写那么长的代码!!很容易出错。

【正确答案】:

144

其实全排列没有错,错的是判断情况的时候。其实只需算出边长相等的情况的次数count,然后再判断这其中包含的重复情况。

重复的情况1、旋转

这里我看错题意了!我以为旋转的意思是9个数字整体顺时针移动1位,真是糊涂啊[捂脸]。这个三角形有3个顶点,旋转的意思应该是从一个顶点到另一个顶点,也就是下面所示:

      A                           	  3                      	  2
     9 6            旋转         	 7 4          旋转       	 8 5
    4   8          ----->       	5   9        ----->         6   7
   3 7 5 2                         2 8 6 A                     A 9 4 3

也就是说,一个数组,就能衍生出其他2种重复的情况,所以总数要除以3,也就是 count / 3 就好了。

重复的情况2、镜像

一开始想的时候我又想复杂了,写了一通代码,结果不知道为何总是运行不了这个方法。

镜像,只有左右两种情况是重复的,count / 2即可。

所以总的来说,count / 3 / 2,就是正确答案了。

第三题、承压计算(已完成)

【题目】:

X星球的高科技实验室中整齐地堆放着某批珍贵金属原料。

每块金属原料的外形、尺寸完全一致,但重量不同。
金属材料被严格地堆放成金字塔形。

                             7 
                            5 8 
                           7 8 8 
                          9 2 7 2 
                         8 1 4 9 1 
                        8 1 8 8 4 1 
                       7 9 6 1 4 5 4 
                      5 6 5 5 6 9 5 6 
                     5 5 4 7 9 3 5 5 1 
                    7 5 7 9 7 4 7 3 3 1 
                   4 6 4 5 5 8 8 3 2 4 3 
                  1 1 3 3 1 6 6 5 5 4 4 2 
                 9 9 9 2 1 9 1 9 2 9 5 7 9 
                4 3 3 7 7 9 3 6 1 3 8 8 3 7 
               3 6 8 1 5 3 9 5 8 3 8 1 8 3 3 
              8 3 2 3 3 5 5 8 5 4 2 8 6 7 6 9 
             8 1 8 1 8 4 6 2 2 1 7 9 4 2 3 3 4 
            2 8 4 2 2 9 9 2 8 3 4 9 6 3 9 4 6 9 
           7 9 7 4 9 7 6 6 2 8 9 4 1 8 1 7 2 1 6 
          9 2 8 6 4 2 7 9 5 4 1 2 5 1 7 3 9 8 3 3 
         5 2 1 6 7 9 3 2 8 9 5 5 6 6 6 2 1 8 7 9 9 
        6 7 1 8 8 7 5 3 6 5 4 7 3 4 6 7 8 1 3 2 7 4 
       2 2 6 3 5 3 4 9 2 4 5 7 6 6 3 2 7 2 4 8 5 5 4 
      7 4 4 5 8 3 3 8 1 8 6 3 2 1 6 2 6 4 6 3 8 2 9 6 
     1 2 4 1 3 3 5 3 4 9 6 3 8 6 5 9 1 5 3 2 6 8 8 5 3 
    2 2 7 9 3 3 2 8 6 9 8 4 4 9 5 8 2 6 3 4 8 4 9 3 8 8 
   7 7 7 9 7 5 2 7 9 2 5 1 9 2 6 5 3 9 3 5 7 3 5 4 2 8 9 
  7 7 6 6 8 7 5 5 8 2 4 7 7 4 7 2 6 9 2 1 8 2 9 8 5 7 3 6 
 5 9 4 5 5 7 5 5 6 3 5 3 9 5 8 9 5 4 1 2 6 1 4 3 5 3 2 4 1 
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 

其中的数字代表金属块的重量(计量单位较大)。
最下一层的X代表30台极高精度的电子秤。

假设每块原料的重量都十分精确地平均落在下方的两个金属块上,
最后,所有的金属块的重量都严格精确地平分落在最底层的电子秤上。
电子秤的计量单位很小,所以显示的数字很大。

工作人员发现,其中读数最小的电子秤的示数为:2086458231

请你推算出:读数最大的电子秤的示数为多少?

注意:需要提交的是一个整数,不要填写任何多余的内容。

【思路】:

题目的意思就是把当前的数字分成两半,然后分别把一半的重量放在下一层的左右两个东西上,然后下一层的数字也这样重复操作。

从头部截取一部分数据来解释一下我的做法:

                            5 8 
                           7 8 8 
                          9 2 7 2 

1、逐行扫描,把一行当成数组。比如扫到上面部分的第一行,记录当前行:

one = {5, 8}

2、然后另外定义一个“分散数组” arr,用来记录当前行分散(平分)后的值,那么会得到

//arr = {2.5, 2.5 + 4, 4}, 5/2 = 2.5, 8/2 = 4
arr = {2.5, 6.5, 4}

3、上面的分散数组即将堆叠到下一层上。继续记录下一行(7,8,8),把这一行的值加上上一行的分散数组的值(即 one[i] = arr[i] + one[i] )。

//one = {7 + 2.5, 8 + 6.5, 8 + 4}
one = {9.5, 14.5, 12}	//这一行的重量,既包括原本该行的重量,又包括上一行的重量

也就是说,下面的金字塔,左侧和右侧是等效的

                  5 8               
                 7 8 8    ========  9.5  14.5  12
                9 2 7 2            9    2    7     2

然后重复以上步骤累计叠加,最后一行获得的数组就是每个电子秤的值。然后在电子秤上寻找最小值,和题目给出的“2086458231”进行比对,肯定会有比例关系,然后在电子秤上寻找最大值乘以这个比例应该就是最终答案了。

值得注意的是,这样除下去会出现很多的小数(金字塔有29层,小数部分起码会超过小数点后20位),这样会影响我们的判断。于是可以把金字塔每个数字都乘以2^29次方,这样就能保证从头除到尾都不会出现小数了,到时候在从输出的结果中判断即可。

【正确答案】:

72665192664

代码如下:

package _8s_LanQiao;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class A3
{
	public static void main(String[] args) throws Exception
	{
		// Text文件
		File file = new File("E:\\1.txt"); 
		// 构造一个BufferedReader类来读取文件
		BufferedReader br = new BufferedReader(new FileReader(file)); 
		String s = null;
		
		//上一个数组
		long[] last = new long[30];

		while ((s = br.readLine()) != null) // 使用readLine方法,一次读一行
		{				
			//拿到一个数组(把当前行变成数组)
			String[] one = s.split(" ");
			
			if(one.length == 1)		//金字塔顶的数组自己赋值,因为下面for循环赋值不了这个只有单个元素的数组
				last[0] = Integer.valueOf(one[0]) * (long)Math.pow(2, 29);
			else
			{
				long[] arr = new long[one.length + 1];	//用来记录"分散后的值"
				for(int i = 0; i < one.length - 1; i++)
				{
					//分散当前数组
					long half = last[i] / 2;	//记录这个数字的一半的值
					arr[i] = arr[i] + half;		//这个一半的值会传给下一层左侧的数字
					arr[i + 1] = arr[i + 1] + half;	//这个一半的值会传给下一层右侧的数字
				}
				//开始叠加到下个数组
				for(int i = 0; i < one.length; i++)
				{
					last[i] = arr[i] + Integer.valueOf(one[i]) * (long)Math.pow(2, 29);
				}
			}
		}
		br.close();
		
		//找到最大值, 这里的最大值就是题目要求出来的值。
		long max = last[0];
		for(int i = 0; i < last.length; i++)
		{
			if(last[i] > max)
				max = last[i];
		}
		System.out.println(max);	//输出72665192664
	}
}

对于上面的代码,我是把金字塔数据放进一个空的txt文本里,然后用网上查的读取文件的方法逐行读取(就是这位大佬的方法),然后构造数组,再进行相应的计算即可。

"1.txt"文件的内容:

7 
5 8 
7 8 8 
9 2 7 2 
8 1 4 9 1 
8 1 8 8 4 1 
7 9 6 1 4 5 4 
5 6 5 5 6 9 5 6 
5 5 4 7 9 3 5 5 1 
7 5 7 9 7 4 7 3 3 1 
4 6 4 5 5 8 8 3 2 4 3 
1 1 3 3 1 6 6 5 5 4 4 2 
9 9 9 2 1 9 1 9 2 9 5 7 9 
4 3 3 7 7 9 3 6 1 3 8 8 3 7 
3 6 8 1 5 3 9 5 8 3 8 1 8 3 3 
8 3 2 3 3 5 5 8 5 4 2 8 6 7 6 9 
8 1 8 1 8 4 6 2 2 1 7 9 4 2 3 3 4 
2 8 4 2 2 9 9 2 8 3 4 9 6 3 9 4 6 9 
7 9 7 4 9 7 6 6 2 8 9 4 1 8 1 7 2 1 6 
9 2 8 6 4 2 7 9 5 4 1 2 5 1 7 3 9 8 3 3 
5 2 1 6 7 9 3 2 8 9 5 5 6 6 6 2 1 8 7 9 9 
6 7 1 8 8 7 5 3 6 5 4 7 3 4 6 7 8 1 3 2 7 4 
2 2 6 3 5 3 4 9 2 4 5 7 6 6 3 2 7 2 4 8 5 5 4 
7 4 4 5 8 3 3 8 1 8 6 3 2 1 6 2 6 4 6 3 8 2 9 6 
1 2 4 1 3 3 5 3 4 9 6 3 8 6 5 9 1 5 3 2 6 8 8 5 3 
2 2 7 9 3 3 2 8 6 9 8 4 4 9 5 8 2 6 3 4 8 4 9 3 8 8 
7 7 7 9 7 5 2 7 9 2 5 1 9 2 6 5 3 9 3 5 7 3 5 4 2 8 9 
7 7 6 6 8 7 5 5 8 2 4 7 7 4 7 2 6 9 2 1 8 2 9 8 5 7 3 6 
5 9 4 5 5 7 5 5 6 3 5 3 9 5 8 9 5 4 1 2 6 1 4 3 5 3 2 4 1 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

运行后,最后一行数组(全部都是0的那一行)得到的结果是:

2928017717
9614247319
14239908242
18309604302
25052706191
31608660493
35591200180
39001205292
45898456845
52477249327
57251678638
62619220562
69215203679
72544032621
72665192664	//最大值
72404929768
68414729111
63620724941
58101345486
50610064018
46361902021
42903151375
39187912500
36994830636
32752069415
26160343661
19595172402
14435118862
8134346317
2086458231	//最小值

这个就是所谓的每个“电子秤”算出的结果,然后我们就观察每个电子秤上的数值,先找到最小值。最小值就是最后一个,即“2086458231”,这就巧了!这不就是题目提到的数值吗!所以我们只需要找到这其中的最大值,肯定就是电子秤的最大值(也就是正确答案)了。

第四题、魔方状态

注:这道题思路是使用bfs把每种情况列举,然后使用set集合去重,这其中代码很复杂(C语言实现都需要120+行代码),如果到了真正考试的时候,我觉得可以放弃这道题了。

【题目】:

二阶魔方就是只有2层的魔方,只由8个小块组成。
如图p1.png所示。

小明很淘气,他只喜欢3种颜色,所有把家里的二阶魔方重新涂了颜色,如下:

前面:橙色
右面:绿色
上面:黄色
左面:绿色
下面:橙色
后面:黄色

请你计算一下,这样的魔方被打乱后,一共有多少种不同的状态。

如果两个状态经过魔方的整体旋转后,各个面的颜色都一致,则认为是同一状态。

请提交表示状态数的整数,不要填写任何多余内容或说明文字。

在这里插入图片描述

第五题、取数位(已完成)

【题目】:

1个整数的第k位数字有很多种方法。
以下的方法就是一种。

public class Main
{
	static int len(int x){
		if(x<10) return 1;
		return len(x/10)+1;
	}
	
	// 取x的第k位数字
	static int f(int x, int k){
		if(len(x)-k==0) return x%10;
		return ______________________;  //填空
	}
	
	public static void main(String[] args)
	{
		int x = 23513;
		//System.out.println(len(x));
		System.out.println(f(x,3));
	}
}

对于题目中的测试数据,应该打印5。

请仔细分析源码,并补充划线部分所缺少的代码。

注意:只提交缺失的代码,不要填写任何已有内容或说明性的文字。

【思路】:

1、先观察len() 函数。

	static int len(int x){
		if(x<10) return 1;
		return len(x/10)+1;
	}

这个函数的作用和它的名字一样,意思是计算x是几位数。因为这是一个递归函数,返回值是len(x / 10) + 1,也就是说每返回一次,x就会少一位(从最低位开始少),同时会计数一次,当x少到不能再少了(x < 10),返回1,然后再逐个递归返回,就可以得到位数。

上面的话有点拗口,刚接触时我也不懂这个函数是什么意思(新手最头疼的就是递归回溯了),仔细了解了一下终于明白了,用下面方式表示可能更直观一点。

计算len(54321)
递归计算:-----------------------------------------------
len(54321) = len(5432) + 1
			 len(5432) = len(543) + 1
			 			 len(543) = len(54) + 1
			 			 			len(54) = len(5) + 1
			 			 					  len(5) = 1
回归计算:-----------------------------------------------		
			 			 			len(54) = 1 + 1 = 2
			 			 len(543) = 2 + 1 = 3
			 len(5432) = 3 + 1 = 4
len(54321) = 4 + 1 = 5

2、明白了len()函数的用法后,再来看看f() 函数。

	static int f(int x, int k){
		if(len(x)-k==0) return x%10;
		return ______________________;  //填空
	}

这里不妨设一个比较极端的例子,x = 321, k = 3作为参数传入执行函数:

f(321,3)

传入参数后,进行if条件判断,可以发现条件成立,直接返回 x % 10 = 1;(从这里发现了,第k位上的数字,其实指的是从左到右数第k个数字)

好的,那么横线上应该填什么呢?我们试着把x = 4321, k = 3传入

f(4321,3)

首先进来,if条件肯定判断不通过,所以需要用到我们填空部分的代码。我们要得到的值是"2",即 f(4321,3) = 2 .那么怎样才能让if条件通过并返回2呢?

我们可以逆推,如果返回的是2,那么这个2一定是在个位,因为只有这样x %10才能等于2。

所以我们的关键在于,如何把4321变成432,很简单,4321 / 10 = 432. 也就是说,我们需要执行f(432, 3),那么就会返回2了。所以需要在 f(4321,3) 里面再执行 f(432,3) 所以填空部分是一个递归,也就是:

	static int f(int x, int k){
		if(len(x)-k==0) return x%10;
		return f(x/10, k);  //填空
	}

【正确答案】:

f(x/10, k);

第六题、最大公共子串(已完成)

【题目】:

最大公共子串长度问题就是:
求两个串的所有子串中能够匹配上的最大长度是多少。

比如:"abcdkkk""baabcdadabc",
可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。

下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。

请分析该解法的思路,并补全划线部分缺失的代码。

public class Main
{
	static int f(String s1, String s2)
	{
		char[] c1 = s1.toCharArray();
		char[] c2 = s2.toCharArray();
		
		int[][] a = new int[c1.length+1][c2.length+1];
		
		int max = 0;
		for(int i=1; i<a.length; i++){
			for(int j=1; j<a[i].length; j++){
				if(c1[i-1]==c2[j-1]) {
					a[i][j] = __________________;  //填空 
					if(a[i][j] > max) max = a[i][j];
				}
			}
		}
		
		return max;
	}
	
	public static void main(String[] args){
		int n = f("abcdkkk", "baabcdadabc");
		System.out.println(n);
	}
}

【思路】:

可以在草稿本上寻找规律,其实举个例子就很容易得出答案了。(下面写的有点复杂,建议拿纸和笔再写一下,挺容易找到规律的。)

代码中提供了两个字符串,把它们分别转化成了数组c1和c2,然后又开辟了一个二维数组a,行是c1.length + 1, 列是 c2.length + 1, 所以我们不妨画个图走一遍代码流程。

		for(int i=1; i<a.length; i++){
			for(int j=1; j<a[i].length; j++){
				if(c1[i-1]==c2[j-1]) {
					a[i][j] = __________________;  //填空 
					if(a[i][j] > max) max = a[i][j];
				}
			}
		}

我们需要判断"abcdkkk"和"baabcdadabc"的最大公共子串,那么开辟的二维数组就是如下表格所示。其中:

1、横向是索引j, 纵向是索引i,自动初始化为0, 由于for循环索引从1开始,所以等于0的部分全部用"-"代替。

2、由于不知道划线部分应该填什么,就暂时把a[i][j]赋值为1作为标记,于是能得到以下表格:

0 1 b 2 a 3 a 4 b 5 c 6 d 7 a 8 d 9 a 10 b 11 c
0 - - - - - - - - - - - -
1 a - 0 1 1 0 0 0 1 0 1 0 0
2 b - 1 0 0 1 0 0 0 0 0 1 0
3 c - 0 0 0 0 1 0 0 0 0 0 1
4 d - 0 0 0 0 0 1 0 1 0 0 0
5 k - 0 0 0 0 0 0 0 0 0 0 0
6 k - 0 0 0 0 0 0 0 0 0 0 0
7 k - 0 0 0 0 0 0 0 0 0 0 0

通过表格可以得知,公共子串在表格中是以对角线形式存在的,而且很容易知道对角线长度其实就是公共子串的长度。因此,为了记录长度,当执行到
a[i][j]=__________时,这里的值应该是沿着对角线自增1,然后变量max会自动记录最大值。因此答案为:
a[i][j] = a[i-1][j-1] + 1;

【正确答案】:

a[i][j] = a[i-1][j-1] + 1;

第七题、日期问题(已完成)

【题目】:

小明正在整理一批历史文献。这些历史文献中出现了很多日期。小明知道这些日期都在
196011日至20591231日。令小明头疼的是,这些日期采用的格式非常不统一,有采用年//日的,
有采用月//年的,还有采用日//年的。更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,
存在很多可能的日期与其对应。  

比如02/03/04,可能是20020304日、20040203日或20040302日。  

给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

输入
----
一个日期,格式是"AA/BB/CC"(0 <= A, B, C <= 9)  

输入
----
输出若干个不相同的日期,每个日期一行,格式是"yyyy-MM-dd"。多个日期按从早到晚排列。  

样例输入
----
02/03/04  

样例输出
----
2002-03-04  
2004-02-03  
2004-03-02  

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗  < 1000ms


请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
不要使用package语句。不要使用jdk1.7及以上版本的特性。
主类的名字必须是:Main,否则按无效代码处理。

【思路】:

现在回过头来看官网对历年真题的解答,发现之前写的代码存在很大的漏洞!很可能超过一半的数据都不能够通过测试。

错误在于yy_MM_dd(int y, int m, int d) 这个方法中。之前在这个方法中我只判断了(day > 31)的情况,殊不知其实还有很多情况需要判断,比如1、3、5、7、8月才有31天,而2月的情况又要分成闰年(29天)平年(28天)

因此,重新修改一下方法yy_MM_dd(int y, int m, int d) 后的具体思路如下:

1、写一个方法 yy_MM_dd(int y, int m, int d), 参数从左到右分别是指年、月、日。在方法中:
①利用参数y构造年份字符串year;
②利用参数m构造月份字符串month,如果不能构造(如m>12),提前return;
③利用参数d构造日的字符串day,如果不能构造(如d>31),提前return;

然后打印(year + month + day),即可出来结果。

1、写一个方法 yy_MM_dd(int y, int m, int d), 参数从左到右分别是指年、月、日

①利用参数y构造年份字符串year;

②利用参数m构造月份的字符串month;如果不能构造(如m>12),提前return;

③利用参数d构造日的字符串day,并进行一系列的判断(如判断该月应该是30天还是31天、该年是闰年还是平年),如果发现不能构造,提前return;

2、输入的形式是 02/03/04,用 数组t 从左到右存储这3个数字。对于输入的日期(如02/03/04),分成以下三种情况(注意方法的参数从左到右是年、月、日):

①这个“日期”有可能是年/月/日的格式。把02当成年,把03当成月,把04当成日,执行一次方法yy_MM_dd(02,03,04),也就是yy_MM_dd(t[0],t[1],t[2]).

②这个“日期”也有可能是月/日/年的格式。把02当成月,把03当成日,把04当成年,执行一次方法yy_MM_dd(04,02,03),也就是yy_MM_dd(t[2],t[0],t[1]).

③这个“日期”也有可能是日/月/年的格式。把02当成日,把03当成月,把04当成年,执行一次方法yy_MM_dd(04,03,02),也就是yy_MM_dd(t[2],t[1],t[0]).

也就是说,在main里执行3次 yy_MM_dd() 方法,只不过3次执行方法时传入的参数顺序不同。

【参考答案】:

package _8s_LanQiao;

import java.util.Scanner;

public class A7
{
	
	//年--月--日的情况
	public static void yy_MM_dd(int y, int m, int d)
	{
		String year, month, day;
		
		//**********年*********
		if(60 <= y && y <= 99)
			year = "19" + y;
		else if(0 <= y && y <=9)
			year = "200" + y;
		else
			year = "20" + y;
		
		//**********月*********
		if(1 <= m && m <= 9)
			month = "0" + m;
		else if(10 <= m && m <= 12)
			month = "" + m;
		else
			return;
		
		//**********日*********
		int max = 30; //记录日的最大值可以是多少(如31、28(平年)、29(闰年)),先初始化为30
		int inty = Integer.valueOf(year);
		
		//逐月判断
		switch(m)
		{
			//如果是二月的情况
			case 2:
				//判断是否是闰年。记住口诀:四年一闰,百年不闰;四百年再闰。
				if( (inty % 4 == 0 && inty % 100 != 0 ) || inty % 400 == 0)
					max = 29;
				else
					max = 28;
				break;
			
			//每月有31天的情况
			case 1: 
			case 3:
			case 5:
			case 7:
			case 8:
			case 10:
			case 12:
				max = 31;
				break;
			
			//其他情况就是每月有30天(已在上面初始化)
		}
		
		if(1 <= d && d <= 9)
			day = "0" + d;
		else if( 10 <= d && d <= max )
			day = "" + d;
		else
			return;
		
		System.out.printf("%s-%s-%s\n", year, month, day);
	}
	
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		String time = sc.nextLine();
		String[] t = time.split("/");	//分割
		
		int aa = Integer.valueOf(t[0]);
		int bb = Integer.valueOf(t[1]);
		int cc = Integer.valueOf(t[2]);
		
		//参数顺序: 年-月-日
		yy_MM_dd(aa, bb, cc);	//可能是 年-月-日
		yy_MM_dd(cc, aa, bb);	//可能是 月-日-年
		yy_MM_dd(cc, bb, aa);	//可能是 日-月-年
		
	}

}

第八题、包子凑数

注:该题代码虽通过了题目给出的测试,但是时间复杂度太高,到真正考试时有可能有些例子会运行超时,等到后面我知道了更优的解法后再回来补充。(考试时这样写应该也能得到一点分的)

建议看官网对该题的讲解!我感觉这道题我的思路不对,以下思路及代码不建议作为解题的参考,建议直接看官网源码(在我自己写的代码后面)。这道题考虑到了使用完全背包(dp)的算法(我还没学习到),具体思路建议到官网看题解。

【题目】:

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中
第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。

每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼
中恰好一共有X个包子。比如一共有3种蒸笼,分别能放345个包子。当顾客想买
11个包子时,大叔就会选23个的再加15个的(也可能选出13个的再加24个的)。

当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能
放456个包子。而顾客想买7个包子时,大叔就凑不出来了。

小明想知道一共有多少种数目是包子大叔凑不出来的。

输入
----
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)  

输出
----
一个整数代表答案。如果凑不出的数目有无限多个,输出INF。

例如,
输入:
2  
4  
5   

程序应该输出:
6  

再例如,
输入:
2  
4  
6    

程序应该输出:
INF

样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。  
对于样例2,所有奇数都凑不出来,所以有无限多个。  

【思路】:

题目简化过来的意思就是:给定几个数(a1,a2, … ,an),由这几个数随意组合凑正整数(这几个数都有无限个),求出不能凑出正整数的数量。
1、比如(3,4)两个数,1、2、5就凑不出来。
6可以凑出来,3+3;
7也可以,3+4;
8 = 4+4;
9 = 3+3+3;
10 = 3+3+4;
11 = 4+4+3;
12 = 4 + 4 + 4;
—分界线—
13 = 3+3+3+4;
14 = 3+3+4+4;
……
2、再举个例子,(4,5)两个数,1、2、3、6、7凑不出来。
8 = 4+4;
9 = 4+5;
10 = 5+5;
11凑不出来。
12 = 4+4+4;
13 = 4+4+5;
14 = 5+5+4;
15 = 5+5+5;
16 = 4+4+4+4;
17 = 4+4+4+5;
18 = 5+5+4+4;
19 = 5+5+5+4;
20 = 5+5+5+5;
—分界线—
21 = 4+4+4+4+5;
22 = 4+4+4+5+5;
……
通过这两个例子,大胆推测,如果两个数(a,b)组合起来,在区间 [a*b, +∞) 内任意整数都可以通过(a, b)凑出来。因此我们只需要考虑区间 (0, a*b] 内有多少个数不能被表示即可。
于是代码具体步骤如下:
1、比如(3,4)两个数,开辟一个长度为3×4的数组。能凑的数用1标记。
2、列一个方程,3x+4y=C,先固定C,然后再固定y,如果得出x有正整数解,说明这个C可以通过3和4凑出来。比如3x + 4 = 7,存在正整数解x = 1,因此7 可以通过3 + 4凑出来。
(以上步骤另写一个方法 comb(int a, int b) 实现)
3、由于输入的数字不一定只有两个,多个数的话,那就两两分组,依次执行以上方法(开辟的数组长度我大胆推测为两个最小的数之积,然后这个数组需要作为“全局变量”,即多组数据共享这个数组),如果参数a和b都是偶数,那么不执行这个方法。

【参考代码】:

以下是自己写的代码,当测试数据只有2个数字时,代码可通过,未测试多于2个数字时的正确性。建议参考官网源码 (在这段代码的后面)

package _8s_LanQiao;

import java.util.Arrays;
import java.util.Scanner;

public class A8
{

	//	ax + by = C -->  x = (C - by)/a
	public static void comb(int a, int b, int[] book)
	{	
		int count = 0;
		
		for(int C = 1; C < book.length; C++)
		{
			for(int y = 0; b*y <= C; y++)
			{
				int x = (C - b * y) / a;
				//成立说明x是正整数,可以表示,同时标记book
				if(a*x + b*y == C && book[C-1] != 1)	
				{
					book[C-1] = 1;
				}
			}
		}
	}
	
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int[] n = new int[N];
		for(int i = 0; i < n.length; i++)
			n[i] = sc.nextInt();
		
		//初始化标记数组
		Arrays.sort(n);
		int[] book = new int[n[0] * n[1]];
		
		//两两组合执行方法。
		for(int p = 0; p < n.length - 1; p++)
		{
			for(int q = p + 1; q < n.length; q++)
			{
				if( !(n[p] % 2 == 0 && n[q] % 2 == 0) )
					comb(n[p],n[q], book);
			}
		}
		
		//算出(0, a*b]区间内有多少个数没被标记(即不能表示)
		int count = 0;
		for(int i = 0; i < book.length; i++)
		{
			if(book[i] == 0)
				count++;
		}
		if(count == book.length)	//如果开辟的数组一个数都没有标记,那么就输出INF
			System.out.println("INF");
		else
			System.out.println(count - 1);
	}

}

【官方题解源码】

public class _08_包子凑数 {

  static int n, g;
  static int[] a = new int[101];
  static boolean[] f = new boolean[10000];

  static int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
  }

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    n = sc.nextInt();
    f[0] = true;
    for (int i = 1; i <= n; ++i) {
      a[i] = sc.nextInt();

      if (i == 1) g = a[i];//初始化最大公约数
      else g = gcd(a[i], g);
      //完全背包的递推
      for (int j = 0; j < 10000 - a[i]; ++j) {
        if (f[j]) f[j + a[i]] = true;
      }
    }

    if (g != 1) {
      System.out.println("INF");
      return;
    }
    //统计个数
    int ans = 0;
    for (int i = 0; i < 10000; ++i) {
      if (!f[i]) {
        ans++;
      }
    }
    System.out.println(ans);
  }
}

第九题、分巧克力(已完成)

【题目】:

    儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
    小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。

    为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:

    1. 形状是正方形,边长是整数  
    2. 大小相同  

例如一块6x5的巧克力可以切出62x2的巧克力或者23x3的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?

输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)  
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000) 
输入保证每位小朋友至少能获得一块1x1的巧克力。   

输出
输出切出的正方形巧克力最大可能的边长。

样例输入:
2 10  
6 5  
5 6  

样例输出:
2

【思路】:

1、按照面积来分。把每块巧克力的面积加起来,然后除以人数,就能得到每个人理论上得到的巧克力的面积(记为sq)。

2、然后把该面积(sq)开根号,得到的数向下取整,作为每块巧克力的边长(记为bian,此时的bian一定是理论上最好的情况)

3、以这个bian为标准,来计算巧克力是否够分。

注意:计算巧克力是否够分时,如果巧克力的最短边小于bian,那么一定要把这个最短边赋值给bian,然后重新计算。

如果不够分,bian–;

4、直到bian减到全部巧克力够分了,输出bian;如果bian等于1,直接输出1;

举例:

1、现有一个6×6的巧克力,8个人分。其总面积为36,理论上每个人得到的巧克力面积为:sq = 36/8 = 4.5

2、sq开根号,得到理论上每个人得到的巧克力的最大边长bian = √4.5 ≈ 2.1213……然后向下取整,即bian = 2;

3、把这个大巧克力的长(为6)除以bian,得到3;把这个巧克力的宽(为6)除以bian,得到3;也就是说,这个巧克力能分出3 × 3 = 9 个bian 为2的巧克力。9 >= 8,够每个人分,直接输出bian = 2。

【参考答案】:

package _8s_LanQiao;

import java.util.Scanner;

public class A9
{

	public static void main(String[] args)
	{
//		System.out.println(Math.sqrt(4.5));
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();	//巧克力个数
		int K = sc.nextInt();	//人数
		int[][] cho = new int[N][2];
		
		int sq = 0;	//统计面积
		for(int i = 0; i < cho.length; i++)	//第i个巧克力
		{
			//0是宽,1是长
			cho[i][0] = sc.nextInt();
			cho[i][1] = sc.nextInt();
			
			sq += cho[i][0] * cho[i][1];
		}
		//返回平方根,强转为int只保留整数部分,相当于向下取整
		int bian = (int)Math.sqrt(sq);
		while(true)
		{
			int count = 0;
			for(int i = 0; i < cho.length; i++)
			{
				int m = cho[i][0] / bian;	//看宽能容纳多大的bian
				int n = cho[i][1] / bian;	//看长能容纳多大的bian
				int min = Math.min(cho[i][0], cho[i][1]);
				
				if(min < bian)	//保证bian小于等于每个巧克力的最短边。
				{
					bian = min;
					i = -1;
					count = 0;
					continue;
				}
				
				count += m * n;
			}
			
			if(count >= K || bian == 1)
				break;
			else
				bian --;
		}
		System.out.println(bian);
	}

}

第十题、k倍区间(已完成)

注:该题代码运用的是暴力法,可能有些测试会超时,如果等到后面知道了更优的解法,再回来补充。

【题目】:

给定一个长度为N的数列,A1, A2, ... AN,如果其中一段连续的子序列Ai, Ai+1, ... Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间。  

你能求出数列中总共有多少个K倍区间吗?  

输入
-----
第一行包含两个整数N和K。(1 <= N, K <= 100000)  
以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)  

输出
-----
输出一个整数,代表K倍区间的数目。  


例如,
输入:
5 2
1  
2  
3  
4  
5  

程序应该输出:
6

【思路】:

想着这个是最后一题了,按照我刷题的速度,考试时写到这一题,时间也应该所剩无几了。所以花了不到10分钟写了一个暴力法。

就是把这个数组的所有公共子串列举出来,然后统计总和,如果总和是k的倍数,计数count++即可。

【参考答案】:

package _8s_LanQiao;

import java.util.Scanner;

public class A10
{

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();	//有多少个元素
		int K = sc.nextInt();
		int[] arr = new int[N];
		for(int i = 0; i < arr.length; i++)
			arr[i] = sc.nextInt();
		
		int count = 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 % K == 0)	//成倍数
					count ++;
			}
		}
		System.out.println(count);
	}

}

总结

刷题其实就是为了积累经验,能够在刷题的过程认识到自己缺失的知识点,那么刷题就是有效果的。以上所有代码仅供参考,不代表官方解法(官方源码除外)。其实代码也不是注重于结果,而是注重刷题时的思考过程,熟悉了各种题目的套路考试起来才能得心应手啊。

原创文章 9 获赞 2 访问量 858

猜你喜欢

转载自blog.csdn.net/cxrshiz7890/article/details/104484800