第一题
美国数学家维纳(N.Wiener)智力早熟,11岁就上了大学。
他曾在1935~1936年应邀来中国清华大学讲学。 一次,他参加某个重要会议,年轻的脸孔引人注目。
于是有人询问他的年龄,他回答说:
“我年龄的立方是个4位数。我年龄的4次方是个6位数。这10个数字正好包含了从0到9这10个数字,每个都恰好出现1次。”
请你推算一下,他当时到底有多年轻。
解法:
此题是固定无输入的填空题,则要贯彻实用性原则,不用考虑算法一定要按照题目中给出的逻辑来设计,而是要其问题的本质:我们的目的是得到Wiener的年龄,所以可以直接通过猜测试探的方法直接打印结果,观察得到答案。
public static int wienerSAge() {
for (int i = 10; i < 30; i++) {
int three = i * i * i;
String a = String.valueOf(three);
String b = String.valueOf(three * i);
if (a.length() == 4 && b.length() == 6) {
StringBuilder sb = new StringBuilder(a);
System.out.println(i);
}
}
return 18;
}
第二题
本题目的要求是:请编写程序,由用户输入若干个罗马数字串,程序输出对应的十进制表示。
输入格式是:第一行是整数n,表示接下来有n个罗马数字(n<100)。
以后每行一个罗马数字。罗马数字大小不超过999。
要求程序输出n行,就是罗马数字对应的十进制数据。
例如,用户输入:
3
LXXX
XCIII
DCCII
则程序应该输出:
80
93
702
解法:
常规来讲,在遇到字符转换这类题目的时候,纯粹想从输入直接转换到输出的算法在竞赛中是不常出现的——因为真要考虑的面面俱到的情况是非常困难的,并且代码编写的时间会非常久。这是非常不划算的。
(一)
我们可以这样考虑,题目给出的输入范围是1-999,并且每位都不存在0,同时我们知道要直接将罗马数字转换成阿拉伯数字是很困难的,我们不妨试试将1-999范围内的阿拉伯数字转换成罗马数字,然后通过比较来得出每个输入的罗马数字对应的阿拉伯数字
这样的算法会相当耗时,但我们在竞赛中的目的就是AC而已,不要太纠结于实用性,并且即使在工作过程中,开发速度也是很重要的,不要太过严谨而忽略了实用性。
public static List<Integer> transFormatRomeNumber(List<String> romeNums) {
Map<Integer, String> map = new HashMap<Integer, String>() {
{
put(1, "I");
put(5, "V");
put(10, "X");
put(50, "L");
put(100, "C");
put(500, "D");
put(1000, "M");
put(4, "IV");
put(9, "IX");
put(40, "XL");
put(90, "XC");
put(400, "CD");
put(900, "CM");
}
};
List<Integer> ret = new ArrayList<>();
for (String romeNum : romeNums) {
for (int i = 1; i <= 999; i++) {
StringBuilder sb = new StringBuilder();
int h = i / 100;
int t = i % 100 / 10;
int o = i % 10;
if (h != 0) {
if (h == 1) sb.append(map.get(100));
if (h == 2) sb.append(map.get(100)).append(map.get(100));
if (h == 3) sb.append(map.get(100)).append(map.get(100)).append(map.get(100));
if (h == 4) sb.append(map.get(400));
if (h == 5) sb.append(map.get(500));
if (h == 6) sb.append(map.get(500)).append(map.get(100));
if (h == 7) sb.append(map.get(500)).append(map.get(100)).append(map.get(100));
if (h == 8) sb.append(map.get(500)).append(map.get(100)).append(map.get(100)).append(map.get(100));
if (h == 9) sb.append(map.get(900));
}
if (t != 0) {
if (t == 1) sb.append(map.get(10));
if (t == 2) sb.append(map.get(10)).append(map.get(10));
if (t == 3) sb.append(map.get(10)).append(map.get(10)).append(map.get(10));
if (t == 4) sb.append(map.get(40));
if (t == 5) sb.append(map.get(50));
if (t == 6) sb.append(map.get(50)).append(map.get(10));
if (t == 7) sb.append(map.get(50)).append(map.get(10)).append(map.get(10));
if (t == 8) sb.append(map.get(50)).append(map.get(10)).append(map.get(10)).append(map.get(10));
if (t == 9) sb.append(map.get(90));
}
if (o != 0) {
if (o == 1) sb.append(map.get(1));
if (o == 2) sb.append(map.get(1)).append(map.get(1));
if (o == 3) sb.append(map.get(1)).append(map.get(1)).append(map.get(1));
if (o == 4) sb.append(map.get(4));
if (o == 5) sb.append(map.get(5));
if (o == 6) sb.append(map.get(5)).append(map.get(1));
if (o == 7) sb.append(map.get(5)).append(map.get(1)).append(map.get(1));
if (o == 8) sb.append(map.get(5)).append(map.get(1)).append(map.get(1)).append(map.get(1));
if (o == 9) sb.append(map.get(9));
}
if (sb.toString().equals(romeNum)) {
ret.add(i);
}
}
}
return ret;
}
(二)
如果使用上面的方法,在遇到规模巨大的输入时(比如上十万,百万个罗马数字),最坏将会执行输入规模*999次换算、比较操作,这是不切实际的,可以说上面的做法是很取巧的。
这里说一下第二种解法,我们注意到在罗马数字中大多是低位在高位右边表示加,反过来表示减。同时,通过观察可以知道逆序的情况非常少,只有4、9、40、90、400、900,这样我们就可以通过“补偿”的思想——即将相同的部分做统一操作,将不同的部分再做另一次统一操作,来实现罗马数字的换算算法。
public static int romeNum(String s){
int sum = 0;
for(int i=0; i<s.length(); i++){
char c = s.charAt(i);
if(c=='I') sum += 1;
if(c=='V') sum += 5;
if(c=='X') sum += 10;
if(c=='L') sum += 50;
if(c=='C') sum += 100;
if(c=='D') sum += 500;
if(c=='M') sum += 1000;
}
// 补偿
if(s.indexOf("IV")>=0) sum -= 2;
if(s.indexOf("IX")>=0) sum -= 2;
if(s.indexOf("XL")>=0) sum -= 20;
if(s.indexOf("XC")>=0) sum -= 20;
if(s.indexOf("CD")>=0) sum -= 200;
if(s.indexOf("CM")>=0) sum -= 200;
return sum;
}
第三题
小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分。
三阶幻方指的是将1~9不重复的填入一个3*3的矩阵当中,使得每一行、每一列和每一条对角线的和都是相同的。
三阶幻方又被称作九宫格,在小学奥数里有一句非常有名的口诀:
“二四为肩,六八为足,左三右七,戴九履一,五居其中”,
通过这样的一句口诀就能够非常完美的构造出一个九宫格来。
4 9 2
3 5 7
8 1 6
有意思的是,所有的三阶幻方,都可以通过这样一个九宫格进行若干镜像和旋转操作之后得到。
现在小明准备将一个三阶幻方(不一定是上图中的那个)中的一些数抹掉,交给邻居家的小朋友来进行还原
并且希望她能够判断出究竟是不是只有一个解。
而你呢,也被小明交付了同样的任务,但是不同的是,你需要写一个程序~
输入格式:
输入仅包含单组测试数据。
每组测试数据为一个3*3的矩阵,其中为0的部分表示被小明抹去的部分。
对于100%的数据,满足给出的矩阵至少能还原出一组可行的三阶幻方。
输出格式:
如果仅能还原出一组可行的三阶幻方,则将其输出,否则输出“Too Many”(不包含引号)。
样例输入
0 7 2
0 5 0
0 3 0
样例输出
6 7 2
1 5 9
8 3 4
解法:
其实作为算法初心者的笔者在看到这道题的时候是有点懵的,甚至会去想比如一行一列如果全为零就肯定是Too Many啊…之类的想法。
但其实这道题根本没有那么复杂,这就是经验不足导致的问题:
我们往往是根据题目给我们的逻辑来思考问题。
这样就没有一个问题转换的过程,在数学中很多问题的推导都是通过将一个复杂的问题简单化到一些已经证明正确的小问题上然后才得出结果。
经过前三题的学习,我们也必须学会在拿到问题之后,不要急着下手,一定要好好思考之后再确定解题逻辑。
好,让我们思考一下,三阶幻方的解法,究竟有多少种呢?
。。。。。。
其实只有8种。
/*
4 9 2
3 5 7
8 1 6
"492357816"
8 3 4
1 5 9
6 7 2
"834159672"
0 7 2
0 5 0
0 3 0
*/
public class A{
static boolean test(String std, String s){
for(int i=0; i<std.length(); i++){
if(std.charAt(i) == s.charAt(i)) continue;
if(s.charAt(i)=='0') continue;
return false;
}
return true;
}
public static void main(String[] args){
String s = "072050030";
String[] ss = {
"492357816",
"834159672",
"618753294",
"276951438",
"294753618",
"438951276",
"816357492",
"672159834"
};
int onlyWay = 0;
String way = null;
for(int i=0; i<ss.length; i++){
if(test(ss[i],s)){
onlyWay++;
way = ss[i];
}
}
if(onlyWay == 1){
System.out.println(way.substring(0,3));
System.out.println(way.substring(3,6));
System.out.println(way.substring(6,9));
}else{
System.out.println("Too Many");
}
}
}
第四题
魔方可以对它的6个面自由旋转。
我们来操作一个2阶魔方(如图1所示):
为了描述方便,我们为它建立了坐标系。
各个面的初始状态如下:
x轴正向:绿
x轴反向:蓝
y轴正向:红
y轴反向:橙
z轴正向:白
z轴反向:黄
假设我们规定,只能对该魔方进行3种操作。分别标记为:
x表示在x轴正向做顺时针旋转
y表示在y轴正向做顺时针旋转
z表示在z轴正向做顺时针旋转
xyz则表示顺序执行x,y,z 3个操作
题目的要求是:
用户从键盘输入一个串,表示操作序列。
程序输出:距离我们最近的那个小方块的3个面的颜色。
顺序是:x面,y面,z面。
例如:在初始状态,应该输出:
绿红白
初始状态下,如果用户输入:
x
则应该输出:
绿白橙
初始状态下,如果用户输入:
zyx
则应该输出:
红白绿
解法:
这道题就是将模仿的六个面展开,并写出每次x、y、z操作对应的面的转换,也就是没什么取巧的地方,直接对数据源进行操作,然后得出结果。
不过在这里我的做法相对较死脑筋一些,而老师给出的做法是对每个面进行了编号,这样三种操作可以直接写死,可以大大减少编码时间。
(一)
/**
* 魔方旋转
*
* @param moves
* @return
*/
public static String pocketCubeRotate(String moves) {
/*
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
String moves = scanner.next();
System.out.println(pocketCubeRotate(moves));
*/
String[][] cube = {
{"", "", "w", "w", "", "", "", ""},
{"", "", "w", "w", "", "", "", ""},
{"o", "o", "g", "g", "r", "r", "b", "b"},
{"o", "o", "g", "g", "r", "r", "b", "b"},
{"", "", "y", "y", "", "", "", ""},
{"", "", "y", "y", "", "", "", ""},
};
char[] movings = moves.toCharArray();
for (char c : movings) {
switch (c) {
case 'x':
String tX1 = cube[1][2];
String tX2 = cube[1][3];
cube[1][2] = cube[3][1];
cube[1][3] = cube[2][1];
cube[3][1] = cube[4][3];
cube[2][1] = cube[4][2];
cube[4][3] = cube[2][4];
cube[4][2] = cube[3][4];
cube[2][4] = tX1;
cube[3][4] = tX2;
String tX3 = cube[2][2];
cube[2][2] = cube[3][2];
cube[3][2] = cube[3][3];
cube[3][3] = cube[2][3];
cube[2][3] = tX3;
break;
case 'y':
String tY1 = cube[0][3];
String tY2 = cube[1][3];
cube[0][3] = cube[2][3];
cube[1][3] = cube[3][3];
cube[2][3] = cube[4][3];
cube[3][3] = cube[5][3];
cube[4][3] = cube[3][6];
cube[5][3] = cube[2][6];
cube[3][6] = tY1;
cube[2][6] = tY2;
String tY3 = cube[2][4];
cube[2][4] = cube[3][4];
cube[3][4] = cube[3][5];
cube[3][5] = cube[2][5];
cube[2][5] = tY3;
break;
case 'z':
String tZ1 = cube[2][0];
String tZ2 = cube[2][1];
cube[2][0] = cube[2][2];
cube[2][1] = cube[2][3];
cube[2][2] = cube[2][4];
cube[2][3] = cube[2][5];
cube[2][4] = cube[2][6];
cube[2][5] = cube[2][7];
cube[2][6] = tZ1;
cube[2][7] = tZ2;
String tZ3 = cube[0][2];
cube[0][2] = cube[1][2];
cube[1][2] = cube[1][3];
cube[1][3] = cube[0][3];
cube[0][3] = tZ3;
break;
}
}
Map<String, String> map = new HashMap<String, String>() {
{
put("w", "白");
put("o", "橙");
put("g", "绿");
put("r", "红");
put("b", "蓝");
put("y", "黄");
}
};
String x = map.get(cube[2][3]);
String y = map.get(cube[2][4]);
String z = map.get(cube[1][3]);
return x + y + z;
}
(二)
/*
二阶魔方的各个面给一个唯一编号:
16 17
19 18
-------
12 13 | 0 1 | 4 5 | 8 9
15 14 | 3 2 | 7 6 | 11 10
-------
20 21
23 22
上
左前右后
下
x操作:(0,1,2,3) (4,21,14,19) (7,20,13,18)
y操作:(4,5,6,7) (1,17,11,21) (2,18,8,22)
z操作:(16,17,18,19) (0,12,8,4) (1,13,9,5)
*/
public class A {
static int[][] transx = {{0, 1, 2, 3}, {4, 21, 14, 19}, {7, 20, 13, 18}};
static int[][] transy = {{4, 5, 6, 7}, {1, 17, 11, 21}, {2, 18, 8, 22}};
static int[][] transz = {{16, 17, 18, 19}, {0, 12, 8, 4}, {1, 13, 9, 5}};
static char[] op(char[] a, int[][] trans) {
char[] b = java.util.Arrays.copyOf(a, a.length);
for (int i = 0; i < trans.length; i++) {
b[trans[i][1]] = a[trans[i][0]];
b[trans[i][2]] = a[trans[i][1]];
b[trans[i][3]] = a[trans[i][2]];
b[trans[i][0]] = a[trans[i][3]];
}
return b;
}
static char[] op(char[] a, String s) {
char[] b = java.util.Arrays.copyOf(a, a.length);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == 'x') b = op(b, transx);
if (s.charAt(i) == 'y') b = op(b, transy);
if (s.charAt(i) == 'z') b = op(b, transz);
}
return b;
}
public static void main(String[] args) {
char[] init = {'绿', '绿', '绿', '绿',
'红', '红', '红', '红',
'蓝', '蓝', '蓝', '蓝',
'橙', '橙', '橙', '橙',
'白', '白', '白', '白',
'黄', '黄', '黄', '黄',};
char[] b = op(init, "xyxyzzxyxyzz");
System.out.println("" + b[1] + b[4] + b[18]);
}
}
第五题
当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?
其实可以不必这么担心,因为并不是一个随便的信用卡号码都是合法的,它必须通过Luhn算法来验证通过。
该校验的过程:
1、从卡号最后一位数字开始,逆向将奇数位(1、3、5等等)相加。
2、从卡号最后一位数字开始,逆向将偶数位数字,先乘以2(如果乘积为两位数,则将其减去9),再求和。
3、将奇数位总和加上偶数位总和,结果应该可以被10整除。
例如,卡号是:5432123456788881
则,奇数位和=35
偶数位乘以2(有些要减去9)的结果:1 6 2 6 1 5 7 7,求和=35。
最后35+35=70可以被10整除,认定校验通过。
请编写一个程序,从键盘输入卡号,然后判断是否校验通过。通过显示:“成功”,否则显示“失败”。
比如,用户输入:356827027232780
程序输出:成功
解法:
Simple,不过注意初始位是0,奇数位判断条件应该是i%2==1,而不是0。
public class A {
// s: 待验证的卡号
static boolean f(String s) {
int sum = 0;
for (int i = 0; i < s.length(); i++) {
int x = s.charAt(s.length() - i - 1) - '0';
if (i % 2 == 1) {
x = x * 2;
if (x >= 10) x -= 9;
}
sum += x;
}
return sum % 10 == 0;
}
public static void main(String[] args) {
System.out.println(f("356827027232780"));
System.out.println(f("356406010024817"));
System.out.println(f("358973017867744"));
System.out.println(f("356827027232781"));
System.out.println(f("306406010024817"));
System.out.println(f("358973017867754"));
}
}