## 前言
本人是大二(二本)计算机系学生,已经报名了下一届的蓝桥杯省赛,整个寒假在家(这次的寒假挺久的哈哈)在b站学习了一些算法(现在会bfs走迷宫、dfs相关算法、递归回溯、常见排列算法),但是还是有很多算法都还不太熟悉,做起题来真是费劲。之前已经在网上刷了几十道题,感觉还可以了,就想体验一下以往考试的试题,就去官网找了一套历年真题来做一下。这次做的是第七届蓝桥杯JavaB组省赛试题。
刷题笔记
注:第八题(四平方和)和第九题(取球博弈)没有做出来,它们分别用到了动态规划和博弈的算法,然而我还没学到……所以,等后面我学习到了这类算法之后再回过头来做!!接下来准备刷下一套题。
第一题、煤球数目(已完成)
【题目】:
有一堆煤球,堆成三角棱锥形。具体:
第一层放1个,
第二层3个(排列成三角形),
第三层6个(排列成三角形),
第四层10个(排列成三角形),
....
如果一共有100层,共有多少个煤球?
【思路】:
思路挺简单,不就是每层递增嘛,第一层1个,第二层1+2个,第三层1+2+3个……第100层就有5050个嘛。是的,我就真以为这道题答案就是5050个了,看来我还是太天真了。正确答案应该是171700个,因为它说的是共有多少个煤球,不是问第100层有多少个煤球。
【正确答案】:
171700个。
public class A1
{
public static void main(String[] args)
{
int sum = 0; //总个数
int ans = 0; //当前层个数
for(int i = 1; i <= 100; i++)
{
ans += i;
sum += ans;
}
System.out.println(sum); //171700
}
}
第二题、生日蜡烛(已完成)
【题目】:
某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。
现在算起来,他一共吹熄了236根蜡烛。
请问,他从多少岁开始过生日party的?
请填写他开始过生日party的年龄数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
【思路】:
之前看过网上类似的题目,因为是填空题,直接暴力法搜索就可以了。还挺简单的。
【正确答案】:
26
public class A2
{
public static void main(String[] args)
{
for(int i = 1; i <= 100; i++) //那就假设它从1岁开始呗
{
int sum = 0;
for(int j = i; j <= 100; j++) //从i岁开始递增
{
sum += j;
if(sum == 236) //如果刚好等于236,输出岁数i
System.out.println(i); //26
}
}
}
}
第三题、凑算式(已完成)
这个算式中A-I代表1-9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。
【思路】:
在网上学的递归全排列终于能用上了!于是乎想着之前学习的模板,写了个全排列:
public class A3
{
static int count = 0;
public static void allSort(int[] arr, int step)
{
if(step == 9)
{
int sum = arr[0] + arr[1] / arr[2] + (arr[3]*arr[4]*arr[5]) / (arr[6]*arr[7]*arr[8]);
if(sum == 10)
count++;
}
for(int i = step; i < arr.length; i++)
{
int temp = arr[i];
arr[i] = arr[step];
arr[step] = temp;
//递归
allSort(arr, step + 1);
//回溯
temp = arr[i];
arr[i] = arr[step];
arr[step] = temp;
}
}
public static void main(String[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
allSort(arr, 0);
System.out.println(count);
}
}
好,答案是24660,感觉没毛病,下一题……
后面回来看正确答案,我才发现做错了!正确答案远比我这个答案小……回来仔细检查,发现了3个致命的错误!!
第一个,算式中的DEF/GHI,意思不是(D×E×F)/(G×H×I),因为每个字母代表一个数字,它表示的是一个三位数除以一个三位数,所以应该是(D×100+E×10+F)/(G×100+H×10+I)
第二个,递归结束条件错误,导致多执行了一次,应该是if(step == arr.length - 1),也就是step = 8的时候就需要判断公式是否相等了。
第三个,也是很致命的错误!之前看网上的辅导视频也有提到,凡是涉及到分数计算的,都是很危险的。并不是说把int换成double就可以了,浮点数在计算机中的表示比较独特,有些分式用计算机算出来的,和实际的可能是不一样的。这一点点误差就可能导致答案错误了。所以应该把分式化为几个数相乘的形式,这样就不会涉及到分数了。
【正确答案】:
29
public class A3
{
static int count = 0;
public static void allSort(int[] arr, int step)
{
if(step == 8) //不能是9
{
int A = arr[0];
int B = arr[1];
int C = arr[2];
int DEF = arr[3] * 100 + arr[4] * 10 + arr[5];
int GHI = arr[6] * 100 + arr[7] * 10 + arr[8];
//if(A + B/C + DEF/GHI == 10) //错误!
if(DEF * C == ((10 - A) * C - B) * GHI) //要乘开来
count++;
}
for(int i = step; i < arr.length; i++)
{
int temp = arr[i];
arr[i] = arr[step];
arr[step] = temp;
allSort(arr, step + 1);
temp = arr[i];
arr[i] = arr[step];
arr[step] = temp;
}
}
public static void main(String[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9 };
allSort(arr, 0);
System.out.println(count); //29
}
}
第四题、分小组(已完成)
【题目】:
9名运动员参加比赛,需要分3组进行预赛。
有哪些分组的方案呢?
我们标记运动员为 A,B,C,... I
下面的程序列出了所有的分组方法。
该程序的正常输出为:
ABC DEF GHI
ABC DEG FHI
ABC DEH FGI
ABC DEI FGH
ABC DFG EHI
ABC DFH EGI
ABC DFI EGH
ABC DGH EFI
ABC DGI EFH
ABC DHI EFG
ABC EFG DHI
ABC EFH DGI
ABC EFI DGH
ABC EGH DFI
ABC EGI DFH
ABC EHI DFG
ABC FGH DEI
ABC FGI DEH
ABC FHI DEG
ABC GHI DEF
ABD CEF GHI
ABD CEG FHI
ABD CEH FGI
ABD CEI FGH
ABD CFG EHI
ABD CFH EGI
ABD CFI EGH
ABD CGH EFI
ABD CGI EFH
ABD CHI EFG
ABD EFG CHI
..... (以下省略,总共560行)。
public class A
{
public static String remain(int[] a)
{
String s = "";
for(int i=0; i<a.length; i++){
if(a[i] == 0) s += (char)(i+'A');
}
return s;
}
public static void f(String s, int[] a)
{
for(int i=0; i<a.length; i++){
if(a[i]==1) continue;
a[i] = 1;
for(int j=i+1; j<a.length; j++){
if(a[j]==1) continue;
a[j]=1;
for(int k=j+1; k<a.length; k++){
if(a[k]==1) continue;
a[k]=1;
System.out.println(__________________________________); //填空位置
a[k]=0;
}
a[j]=0;
}
a[i] = 0;
}
}
public static void main(String[] args)
{
int[] a = new int[9];
a[0] = 1;
for(int b=1; b<a.length; b++){
a[b] = 1;
for(int c=b+1; c<a.length; c++){
a[c] = 1;
String s = "A" + (char)(b+'A') + (char)(c+'A');
f(s,a);
a[c] = 0;
}
a[b] = 0;
}
}
}
仔细阅读代码,填写划线部分缺少的内容。
注意:不要填写任何已有内容或说明性文字。
【思路】:
1、初步看来,也是个递归全排列的形式。它把数组用0-1进行标记,然后在输出。不过我作为刚入门的新手,自然是不能完全看懂这段代码的。但是,我可以一个个套呀,
划线部分在 f(String s, int[] a) 函数里,可是这个参数“s”却没有用上,所以推断划线部分肯定要加上s。只填一个s测试一下,
System.out.println(s);
发现输出:
ABC
…… //n个ABC
ABC
ABD
…… //n个ABD
ABD
ABE
…… //n个ABE
ABE
……
这一部分不就是和样例输出的开头一样嘛!所以推断划线部分开头肯定是输出“s”这个参数。
2、然后再仔细观察,代码里多了一个remain(int[] a) 函数,而代码其他地方并没有使用到这个函数。所以,划线部分一定要填这个函数。能怎么填?直接测试:
System.out.println(remain(a));
发现输出:
GHI
FHI
FGI
FGH
EHI
EGI
EGH
EFI
EFH
EFG
DHI
……
这不就是样例输出的后面部分嘛!所以这个函数的输出一定是填在后面部分的。
3、最后观察一下,中间部分哪去了呢?
f(String s, int[] a) 这个函数里,是3个嵌套循环。仔细一想,这有啥用?很可能是根据3个循环的索引(i,j,k)用来直接输出的。按照这个想法,我填了:
//(char)(i+'A')就是把不同的i变化成不同的字母,代码的其他地方可以体现。
System.out.println("" + (char)(i+'A') + (char)(j+'A') + (char)(k+'A'));
输出:
DEF
DEG
DEH
DEI
DFG
DFH
DFI
DGH
……
哈哈哈哈,正是中间的部分,所以这道题就已经解决了!
【正确答案】:
s + " " + (char)(i+'A') + (char)(j+'A') + (char)(k+'A') + " " + remain(a)
第五题、抽签(已完成)
【题目】:
X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
....
那么最终派往W星的观察团会有多少种国别的不同组合呢?
下面的程序解决了这个问题。
数组a[] 中既是每个国家可以派出的最多的名额。
程序执行结果为:
DEFFF
CEFFF
CDFFF
CDEFF
CCFFF
CCEFF
CCDFF
CCDEF
BEFFF
BDFFF
BDEFF
BCFFF
BCEFF
BCDFF
BCDEF
....
(以下省略,总共101行)
public class A
{
public static void f(int[] a, int k, int n, String s)
{
if(k==a.length){
if(n==0) System.out.println(s);
return;
}
String s2 = s;
for(int i=0; i<=a[k]; i++){
_____________________________; //填空位置
s2 += (char)(k+'A');
}
}
public static void main(String[] args)
{
int[] a = {4,2,2,1,1,3};
f(a,0,5,"");
}
}
仔细阅读代码,填写划线部分缺少的内容。
注意:不要填写任何已有内容或说明性文字。
【思路】:
应该也是一种全排列,但是看懂代码是不可能看懂的……所以只能上下文去找答案了。观察一下f(int[] a, int k, int n, String s) 这个函数,形式特别像全排列递归算法,所以这里肯定是用到递归了。整个函数递归的语句都没出来,所以填空部分肯定要填递归语句。
1、起初推测:
观察一下递归结束条件,就是上面的if判断语句,发现结束条件是k==a.length,以及n == 0; 所以,k一定是每层递归都加1,n一定是每层递归都减1。因为main函数里传入的参数 f(a,0,5,"") ,k是从0开始的,n是从5开始的,所以说递归时,k一定是增,n一定是减。
于是乎,尝试性地填了个答案测试:
f(a, k + 1, n - 1, s);
输出:
。。。
没有输出!
2、再推测:
肯定是某个参数有问题了。会不会是“s”有错?因为这个函数里,字符串就只有2个变量,一个是s,另一个是s2
但是很快我就排除了s了,因为s2在for循环中反复使用了,而s是固定不动的,所以传入参数时肯定是传入变化的参数,而不是不动的参数。因此:
f(a, k + 1, n - 1, s2);
输出:
。。。
还是没有输出……
3、再再再推测:
已经基本确定a,k + 1, s2参数是正确的,那问题就出在n - 1上了。很明显递归的时候n是逐渐在减呀,然后我灵机一动,把n - 1改成了 n - i,结果就发现答案正确了!!!
总结一句话:填空题如果看不懂代码,多观察多测试,总会测出正确答案的!
【正确答案】:
f(a, k + 1, n - i, s2);
第六题、方格填数(已完成)
【题目】:
如下的10个格子
填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
【思路】:
第一眼看这个问题,嗯,像极了高中时做的排列组合……但是这个远比正常的排列组合题难(虽然我觉得用排列组合也可以做出来)。没办法,作为新手的我只能求助于网上大佬的文章。
好的,看完了,其实套路和下面的第七题一样,都是先把所有组合列出来,然后判断这个组合是否符合条件。找出所有符合条件的情况即可。
【正确答案】:
1580
自己写了个代码,代码包括两部分,第一个是生成全排列数组f(),第二个是用于判断这种组合情况是不是符合题意(即每个数字和上下左右以及↖↗↘↙的数字是不是连续的)
代码注释我尽量说明白,不然下次复习的时候看不懂自己写的啥……
package _7s_LanQiao;
/*
* 方格填数
*/
public class A6
{
/*
把原来的格子扩充为长度为30,把周边用-1围起来,这样边界就容易判断了
其实map中间有效部分初始化时可以填任意数字(-1不行),我把它初始化成8 9 10……
是为了好看,8 9 10……其实就是这个值对应的索引。
最终map的中间部分会被node[]代替,然后用check传入判断这种map合不合法。
*/
static int[] map = {-1, -1, -1, -1, -1, -1,
-1, -1, 8, 9, 10, -1,
-1, 13, 14, 15, 16, -1,
-1, 19, 20, 21, -1, -1,
-1, -1, -1, -1, -1, -1};
//如果不懂这个方向是怎么来的,看一下上图的[15],然后用它分别加下面的值,就懂了。
static int[] d = {-6, 1, 6, -1, -7, -5, 7, 5}; //定义方向, 上右下左,↖↗↘↙
static int count = 0;
/*
传入一种map的组合,判断其四周是否有相邻的数字,比如下面这个map就不行,
因为7和8、8和9连续了,check函数可以找到7和8连续和8和9连续(只要有一个连续,就)返回false。
{-1, -1, -1, -1, -1, -1,
-1, -1, 0, 3, 2, -1,
-1, 6, 8, 7, 5, -1,
-1, 9, 4, 1, -1, -1,
-1, -1, -1, -1, -1, -1};
*/
public static boolean check(int[] map)
{
// print(map);
for(int i = 8; i <= 21; i++) //一个个遍历格子
{
if(map[i] == -1)
continue;
//这个格子的八个方向的格子都要判断
for(int j = 0; j < 8; j++)
{
int next = i + d[j]; //其中一个方向,next是索引
//判断边界
if(8 <= next && next <= 21 && map[next] != -1)
{
//如果其中一个方向有连续的数字相邻的情况
if((int)Math.abs(map[next] - map[i]) == 1)
{
return false;
}
}
}
}
return true;
}
//全排列递归算法,得到所有的组合node,然后把每一种node都贴到map里面,用check检查
public static void f(int[] node, int step)
{
//获取一种组合
if(step == node.length - 1)
{
// print(map);
//把这种组合与map合并
int index = 0;
for(int i = 8; i <= 21; i++)
{
if(map[i] != -1)
{
map[i] = node[index++];
}
}
// print(map);
//判断这种map是否符合标准
if(check(map))
count++;
}
for(int i = step; i < node.length; i++)
{
int temp = node[i];
node[i] = node[step];
node[step] = temp;
f(node, step + 1);
temp = node[i];
node[i] = node[step];
node[step] = temp;
}
}
public static void main(String[] args)
{
int[] node = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //有效的组合(索引)
//对node进行全排列, 每种情况用check判断。
f(node, 0);
System.out.println(count);
}
}
第七题、剪邮票(已完成)
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
【思路】:
这道题我知道可能要用dfs才能解,但是。。并无从下手,所以只能求助于网上大佬。仔细阅读下面大佬的思路,算是明白了怎么做这道题了。
https://blog.csdn.net/u014552756/article/details/50946197
其实做这道题时,我一开始的想法是在这个图中依次以一个格子为结点,进行dfs递归,然后列出所有的方法。但是这样太过于麻烦,而且每个结点都递归,很可能会把重复的情况算上,然后去重又是一件难事。
其实反过来想,一共就12个格子,从12个格子里取5个,所有取法是可以列举出来的,然后再用dfs判断每种取法的格子是否相互连接(比如12345是相互连接的,但是12349却不是),没有相互连接的情况去掉即可。
【正确答案】:
116
我把代码再补充一下注释吧……免得以后复习不知道自己写的啥。
package _7s_LanQiao;
/*
* 剪邮票,把数组定义为如下,可以巧妙地避免过多的边界判断
* 1 2 3 4
* 6 7 8 9
* 11 12 13 14
*/
public class A7
{
static int[] d = {-5, 1, 5, -1}; //方向,上右下左
static int[] book = new int[5];
public static void dfs(int[] node, int start, int step)
{
//四个方向走一遍
for(int i = 0; i < 4; i++)
{
int next = start + d[i];
//判断边界(左右边界、上下边界)、判断是否标记(1标记,0无)
if(next != 5 && next != 10 && 0 <= next && next <= 14)
{
for(int j = 0; j < node.length; j++)
{
//这个结点可以连接下一个结点, 把下个结点标记, 递归
if(book[j] != 1 && node[j] == next)
{
book[j] = 1;
dfs(node, next, step + 1);
}
}
}
}
}
public static void main(String[] args)
{
int[] map = {1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14};
int[] node = new int[5]; //存放组合
int count = 0;
//寻找全部组合
for(int a = 0; a < map.length; a++)
for(int b = a + 1; b < map.length; b++)
for(int c = b + 1; c < map.length; c++)
for(int d = c + 1; d < map.length; d++)
for(int e = d + 1; e < map.length; e++)
{
node[0] = map[a];
node[1] = map[b];
node[2] = map[c];
node[3] = map[d];
node[4] = map[e];
book[0] = 1;
//把组合用dfs判断是否符合条件
dfs(node, node[0], 0); //传入组合、初始结点、步数
boolean can = true;
for(int i = 0; i < book.length; i++)
{
if(book[i] == 0) //组合中有没被标记到的(也就是组合不合条件)
can = false;
}
if(can)
count++;
//清空标记
for(int i = 0; i < book.length; i++)
book[i] = 0;
}
System.out.println(count); //116
}
}
这里我用的是book数组标记一种组合是否满足条件。
正常情况下,如果组合是满足条件的(即格子相互连接),那么dfs是可以把这个组合的结点(通过上下左右)全部访问的,访问后记为1,如果book数组全部都是1,说明这种组合符合条件(即格子相互连接)。
如果book数组出现了没有标记的地方(比如1,1,1,1,0),说明最后一个结点dfs访问不到,那么格子肯定是断开了,那么这种情况不符合。
相比之下感觉第六题的方法更好一点,就是扩充数组,把周围用-1围起来。
第八题、四平方和(已完成)
【题目】:
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多4个正整数的平方和。
如果把0包括进去,就正好可以表示为4个数的平方和。
比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思)
对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对4个数排序:
0 <= a <= b <= c <= d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000)
要求输出4个非负整数,按从小到大排序,中间用空格分开
例如,输入:
5
则程序应该输出:
0 0 1 2
再例如,输入:
12
则程序应该输出:
0 2 2 2
再例如,输入:
773535
则程序应该输出:
1 1 267 838
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
【思路】:
暴力解法,暴力枚举a,b,c,d,但是只要巧妙地限定for循环的条件,我发现运行时间比官方题解的代码还快!我也不清楚为什么,下面直接上代码。
【参考答案】:
import java.util.Scanner;
public class A8
{
//自己写的暴力法
public static void four(int N)
{
for(int a = 0; a*a <= N; a++)
{
for(int b = a; b*b <= N; b++)
{
for(int c = b; c*c <= N; c++)
{
for(int d = c; d*d <= N; d++)
{
int pa = (int)Math.pow(a, 2);
int pb = (int)Math.pow(b, 2);
int pc = (int)Math.pow(c, 2);
int pd = (int)Math.pow(d, 2);
if(pa + pb + pc + pd == N)
{
System.out.println(a + " " + b + " " + c + " " + d);
return;
}
}
}
}
}
}
//根据官方题解思路加上相关注释:
public static void four2(int N)
{
//a^2 + b^2 + c^2 + d^2 = N, 令c^2 + d^2 = x;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//找出所有x,存入哈希表,一个x对应一个c,拿到c之后,d = √(x - c*c)
for(int c = 0; c*c <= N/2; c++)
{
for(int d = c; c*c + d*d <= N; d++)
{
if(map.get(c*c + d*d ) == null)
map.put(c*c + d*d, c);
}
}
//暴力枚举a,b
for(int a = 0; a*a <= N/4; a++)
{
for(int b = a; a*a + b*b <= N/2; b++)
{
if(map.get(N - a*a - b*b) != null) //能找到x
{
int c = map.get(N - a*a - b*b);
int d = (int)Math.sqrt(N - c*c - a*a - b*b);
System.out.printf("%d %d %d %d", a, b, c, d);
return; //输出的第一个一定是符合题意的,输出后中止程序运行即可。
}
}
}
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
four(N);
four2(N);
}
}
第九题、取球博弈
(没做出来,待更新……)
【题目】:
取球博弈
两个人玩取球的游戏。
一共有N个球,每人轮流取球,每次可取集合{n1,n2,n3}中的任何一个数目。
如果无法继续取球,则游戏结束。
此时,持有奇数个球的一方获胜。
如果两人都是奇数,则为平局。
假设双方都采用最聪明的取法,
第一个取球的人一定能赢吗?
试编程解决这个问题。
输入格式:
第一行3个正整数n1 n2 n3,空格分开,表示每次可取的数目 (0<n1,n2,n3<100)
第二行5个正整数x1 x2 … x5,空格分开,表示5局的初始球数(0<xi<1000)
输出格式:
一行5个字符,空格分开。分别表示每局先取球的人能否获胜。
能获胜则输出+,
次之,如有办法逼平对手,输出0,
无论如何都会输,则输出-
例如,输入:
1 2 3
1 2 3 4 5
程序应该输出:
+ 0 + 0 -
再例如,输入:
1 4 5
10 11 12 13 15
程序应该输出:
0 - 0 + +
再例如,输入:
2 3 5
7 8 9 10 11
程序应该输出:
+ 0 0 0 0
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
第十题、压缩变换(已完成)
【题目】:
小明最近在研究压缩算法。
他知道,压缩的时候如果能够使得数值很小,就能通过熵编码得到较高的压缩比。
然而,要使数值很小是一个挑战。
最近,小明需要压缩一些正整数的序列,这些序列的特点是,后面出现的数字很大可能是刚出现过不久的数字。对于这种特殊的序列,小明准备对序列做一个变换来减小数字的值。
变换的过程如下:
从左到右枚举序列,每枚举到一个数字,如果这个数字没有出现过,刚将数字变换成它的相反数,如果数字出现过,则看它在原序列中最后的一次出现后面(且在当前数前面)出现了几种数字,用这个种类数替换原来的数字。
比如,序列(a1, a2, a3, a4, a5)=(1, 2, 2, 1, 2)在变换过程为:
a1: 1未出现过,所以a1变为-1;
a2: 2未出现过,所以a2变为-2;
a3: 2出现过,最后一次为原序列的a2,在a2后、a3前有0种数字,所以a3变为0;
a4: 1出现过,最后一次为原序列的a1,在a1后、a4前有1种数字,所以a4变为1;
a5: 2出现过,最后一次为原序列的a3,在a3后、a5前有1种数字,所以a5变为1。
现在,给出原序列,请问,按这种变换规则变换后的序列是什么。
输入格式:
输入第一行包含一个整数n,表示序列的长度。
第二行包含n个正整数,表示输入序列。
输出格式:
输出一行,包含n个数,表示变换后的序列。
例如,输入:
5
1 2 2 1 2
程序应该输出:
-1 -2 0 1 1
再例如,输入:
12
1 1 2 3 2 3 1 2 2 2 3 1
程序应该输出:
-1 0 -2 -3 1 1 2 2 0 0 2 2
数据规模与约定
对于30%的数据,n<=1000;
对于50%的数据,n<=30000;
对于100%的数据,1 <=n<=100000,1<=ai<=10^9
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
【思路】:
我还以为最后一题是压轴题,特别难的那种。结果没想到没到15分钟就做出来了,其实思路还挺好想的。
1、先弄明白题目在讲什么。
比如这么一个数列,[1 2 2 1 2],意思就是,从左到右扫描这个数列,通过特定的条件进行替换。
①先扫到索引0,判断这个数“1”在之前有没有出现,没有出现过,那么就用相反数替换。
结果[-1 2 2 1 2](原数列[1 2 2 1 2])
②然后扫到索引1,这个数“2”在之前没有出现过,用相反数替换。
结果[-1 -2 2 1 2](原数列[1 2 2 1 2])
③扫到索引2,这个数“2”在之前出现过了,最后一次出现是在索引1,索引1和索引2之间有0种不同的数字,那么用0替换该位置。
结果[-1 -2 0 1 2](原数列[1 2 2 1 2])
④扫到索引3,这个数“1”在之前出现过了,最后一次出现是在索引0,索引0和索引3之间有1种不同的数字,那么用1替换该位置。
结果[-1 -2 0 1 2](原数列[1 2 2 1 2])
⑤扫到索引4,这个数“2”在之前出现过了,最后一次出现是在索引2,索引2和索引4之间有1种不同的数字,那么用1替换该位置。
结果[-1 -2 0 1 1](原数列[1 2 2 1 2])
(回过头看一下我上面说的这个步骤,我感觉……还不如直接看题目了解的更快)
2、想想用什么方法解决算法
首先一个整体框架肯定就是用for循环遍历这个数字,但是同时也要记录下每个数字所在位置以及出现过的次数。记录每个数字所在位置使用Map<Integer, Integer>来记录(key=元素,value=索引),记录出现的次数使用Set<Integer>(每次都把元素添加进去,因为set会自动去重,所以最后输出的长度是多少就说明有多少种数字)。这个咋说明呢,有点难……还是直接上代码吧:
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.HashSet;
public class _7s_Compression
{
public static void f(int[] arr)
{
int[] temparr = arr.clone();
//序列, 索引,记录最后一个位置
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//不重复序列,记录种类
Set<Integer> set = new HashSet<Integer>();
int temp;
for(int i = 0; i < arr.length; i++)
{
if(!map.containsKey(arr[i])) //如果没出现过
temp = arr[i] * -1;
else
{
for(int j = map.get(arr[i]) + 1; j < i; j++)
{
//看种类时,是看原数组的种类,不是新数组的种类!!
set.add(temparr[j]); //看有多少种类
}
temp = set.size();
set.clear();
}
//把当前元素添加到哈希表
map.put(arr[i], i);
//执行替换操作
arr[i] = temp;
}
}
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; i++)
arr[i] = sc.nextInt();
f(arr);
for(int i = 0; i < n; i++)
System.out.print(arr[i] + " ");
}
}