暴力破解是大赛以及企业应用中常用的方法之一,往往大部分题目都可以适用于暴力破解,直接、准确,不在乎结构的美观,但是效率高,方法简单直接,易想到。
但可能时间复杂度和空间复杂度要更高。
真题:年龄谜题
美国数学家维纳(N.Wiener)智力早熟,11 岁就上了大学。他曾在 1935~1936 年应邀来中国清华大学讲学。一次,他参加某个重要会议,年轻的脸孔引人注目。于是有人询问他的年龄,他回答说:“我年龄的立方是个 4 位数。我年龄的 4 次方是个6 位数。这 10 个数字正好包含了从 0 到 9 这 10 个数字,每个都恰好出现 1 次。”
请你推算一下,他当时到底有多年轻。
我们可以直接对题目进行抽象化,形成计算机逻辑语言,直接完成实现题目:
首先,我们知道年龄是在0-100之间的,所以按照题目要求直接在0-100区间内进行暴力破解:
暴力破解框架:
for(int i=0; i<100; i++){
s = 把立方与 4 次方拼串
if(test(s)==false) continue;
print(i);
}
实际完成:
public class yearsOld {
public static void main(String[] args) {
for(int i = 0;i<100;i++) {
int a= i*i*i;
int b=i*i*i*i;
if((a+"").length()!=4) {
continue;
}else if((b+"").length()!=6) {
continue;
}else {
System.out.println(i+" = "+a+" "+b);
}
}
}
}
我们可以看到仅仅一个条件就把答案缩小在了一个很小的区间:
接下来就可以继续考虑之后的条件了。
检测重复的思路
第一种思路是建立一个包含十个元素的数组,检测 0-9 每个数字出现的次数。这种方法称作不重复。
还有一个写法检测 0-9 是否在前面得到的串中。这种方法称作不遗漏。
这两种方法在时间和空间上等价。
根据题目中“我年龄的立方是个 4 位数。我年龄的 4 次方是个6 位数。”的信息,可以从暴力破解的框架中再筛选,这样就可以提高暴力破解的时间和空间利用率。
完整解题:
public class yearsOld {
public static void main(String[] args) {
for(int i = 18;i<22;i++) {//筛选
int a= i*i*i;
int b= i*i*i*i;
if((a+"").length()!=4) {
continue;
}else if((b+"").length()!=6) {
continue;
}
String s = (a+""+b);
int[] times = new int[10];
char[] arr = s.toCharArray();
for(int k = 0;k<10;k++)
for(int j = 0;j<10;j++) {
if(arr[j] == (k+48)) {
times[k]++;//不重复
}
}
int l =0;
for(;l<10;l++) {
if(times[l]!=1) {
break;
}
}
if(l>=10){
System.out.println("years"+"="+s);
}
}
}
}
不要唯美主义,暴力破解的目标是:实用、快速、稳定、有效
例题:
罗马数字
古罗马帝国开创了辉煌的人类文明,但他们的数字表示法的确有些繁琐,尤其在示
大数的时候,现在看起来简直不能忍受,所以在现代很少使用了。之所以这样,不
是因为发明表示法的人的智力的问题,而是因为一个宗教的原因,当时的宗教禁止
在数字中出现 0 的概念!罗马数字的表示主要依赖以下几个基本符号:
I --> 1
V --> 5
X --> 10
L --> 50
C --> 100
D --> 500
M --> 1000
这里,我们只介绍一下 1000 以内的数字的表示法。
单个符号重复多少次,就表示多少倍。最多重复 3 次。
比如:CCC 表示 300 XX 表示 20,但 150 并不用 LLL 表示,这个规则仅适用于 I X C M。
如果相邻级别的大单位在右,小单位在左,表示大单位中扣除小单位。
比如:IX 表示 9 IV 表示 4 XL 表示 40
49 = XLIX
思路:
我们可以想到使用数组以下标表示级层,存入I、V、X、C、D、M分别表示1,5,10,50,100,500,1000。在遇到响应字母时直接对其对应下标乘以对应权重。
但这种方法占用内存过多,考虑较多,时间和空间上来说都不适合,我们可以直接对其枚举式的判断选择。
设置一个sum,遇到对应字母,直接加上对应数值。
但此时仅仅,只加会有偏差,我们看题中条件有“IV”、“IX”、“XL”的异常值。我们可以加入偏差修正,对其差出的值,减去,保证返回的是正确值即可。
完整解法:
import java.util.Scanner;
public class romeNumber {
private static int romNum(String s) {
int sum = 0;
char[] arr = s.toCharArray();
for(int i = 0;i<s.length();i++) {
if(arr[i]=='I') sum += 1;
if(arr[i]=='V') sum += 5;
if(arr[i]=='X') sum += 10;
if(arr[i]=='L') sum += 50;
if(arr[i]=='C') sum += 100;
if(arr[i]=='D') sum += 150;
if(arr[i]=='M') sum += 1000;
}
//修正
if(s.indexOf("IV") >= 0 ) sum -= 2;
if(s.indexOf("IX") >= 0 ) sum -= 2;
if(s.indexOf("XL") >= 0 ) sum -= 20;
return sum;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
System.out.println(romNum(s));
}
}
类似于这样拥有特定规律,需要考虑元素量很多、但规模有限的问题,可以考虑使用枚举法。
对于上述罗马数字问题,都是建立在所选案例都是正确罗马数的条件下的。
那么如果从一开始输入的字符串就是错误的字符串排列顺序呢?这就需要我们做一个过滤,去除错误情况。
现在我们的问题就成为了如何判断一个字符串是不是罗马数字呢?
如果我们还是按照上面的枚举方法,强行暴力分析各种情况,那么就会更加复杂,并且还会容易遗漏未想到的地方。
我们不妨利用逆向思维,反过来考虑一下,数字是怎么变成罗马数字的?我们只要知道了罗马数字有哪些,那么其他的就不是罗马数字了,很容易就能判断字符串是否为罗马数字。
static String numToRom(int x) {
String s = "";
int th = x / 1000;
int hu = x % 1000 / 100;
int te = x % 100 / 10;
int on = x % 1000;
if(th == 3) s += "MMM";
if(th == 2) s += "MM";
if(th == 1) s += "M";
if(hu == 9) s += "CM";
if(hu == 8) s += "DCCC";
if(hu == 7) s += "DCC";
if(hu == 6) s += "DC";
if(hu == 5) s += "D";
if(hu == 4) s += "CD";
if(hu == 3) s += "CCC";
if(hu == 2) s += "CC";
if(hu == 1) s += "C";
if(te == 9) s += "XC";
if(te == 8) s += "LXXX";
if(te == 7) s += "LXX";
if(te == 6) s += "LX";
if(te == 5) s += "L";
if(te == 4) s += "XL";
if(te == 3) s += "XXX";
if(te == 2) s += "XX";
if(te == 1) s += "X";
if(on == 9) s += "IX";
if(on == 8) s += "VIII";
if(on == 7) s += "VII";
if(on == 6) s += "VI";
if(on == 5) s += "V";
if(on == 4) s += "IV";
if(on == 3) s += "III";
if(on == 2) s += "II";
if(on == 1) s += "I";
return s;
}
通过以上代码,可以返回一个正规的罗马数字,只要我们将输入的字符串与该生成的正规罗马数作比较,就可以判断输入的是否为罗马数了。
import java.util.Scanner;
public class romaNumber2 {
static String numToRom(int x) {
String s = "";
int th = x / 1000;
int hu = x % 1000 / 100;
int te = x % 100 / 10;
int on = x % 1000;
if(th == 3) s += "MMM";
if(th == 2) s += "MM";
if(th == 1) s += "M";
if(hu == 9) s += "CM";
if(hu == 8) s += "DCCC";
if(hu == 7) s += "DCC";
if(hu == 6) s += "DC";
if(hu == 5) s += "D";
if(hu == 4) s += "CD";
if(hu == 3) s += "CCC";
if(hu == 2) s += "CC";
if(hu == 1) s += "C";
if(te == 9) s += "XC";
if(te == 8) s += "LXXX";
if(te == 7) s += "LXX";
if(te == 6) s += "LX";
if(te == 5) s += "L";
if(te == 4) s += "XL";
if(te == 3) s += "XXX";
if(te == 2) s += "XX";
if(te == 1) s += "X";
if(on == 9) s += "IX";
if(on == 8) s += "VIII";
if(on == 7) s += "VII";
if(on == 6) s += "VI";
if(on == 5) s += "V";
if(on == 4) s += "IV";
if(on == 3) s += "III";
if(on == 2) s += "II";
if(on == 1) s += "I";
return s;
}
static boolean romaNumberJurge(String s) {
for(int i = 0;i < 4000;i++) {
if(s.equals(numToRom(i))) {
return true;
}
}
return false;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
System.out.println(romaNumberJurge(s));
}
}
真题实例:
小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分。
三阶幻方指的是将 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
首先我们看到题目,总会想到对已经给出的标准如何旋转和镜像操作。
实际上我们可以发现,对于已经给出标准三阶幻方,只有8种解,那么我们可以将八种解按照上述枚举解法,一一列举出来,用输入的残缺幻方对比,就可以得到准确答案了。
例如:
4 9 2
3 5 7
8 1 6
顺时针旋转一次:
8 3 4
1 5 9
6 7 2
以此类推,镜像也是如此,共有八种解
那么,我们就可以不必使用二维数组来表示,只需用一维数组,甚至可以用字符串来表示八种解。
String[] srr = {
"492357816",
"834159672",
"618753294",
"296951438",
"816357492",
"294753618",
"672159834"
};
此时我们有了标准答案,只需要按题目要求,对比输入的残缺九宫格是否能与答案对应,如果可以,就输出完整的标准答案。
完整解决方案:
import java.util.Scanner;
public class magicSquare {
public static boolean equals(String s1,String s2) {
for(int i =0;i<s1.length();i++) {
if(s1.charAt(i)==s2.charAt(i)) continue;
else if(s2.charAt(i)=='0') continue;
else return false;
}
return true;
}
public static void main(String[] args) {
String[] srr = {
"492357816",
"834159672",
"618753294",
"296951438",
"816357492",
"294753618",
"672159834"
};
Scanner sc = new Scanner(System.in);
String s = sc.next();
for(int i = 0;i<srr.length;i++) {
if(equals(srr[i],s)) {
System.out.print(srr[i]);
}else {
System.out.println("Too Many!");
}
}
}
}
【问题描述】 长100厘米的细长直杆子上有n只蚂蚁。它们的头有的朝左,有的朝右。 每只蚂蚁都只能沿着杆子向前爬,速度是1厘米/秒。
当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。 这些蚂蚁中,有1只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。
请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
【数据格式】
第一行输入一个整数n (1 < n < 50), 表示蚂蚁的总数。
接着的一行是n个用空格分开的整数 Xi (-100 < Xi < 100), Xi的绝对值,表示蚂蚁离开杆子左边端点的距离。正值表示头朝右,负值表示头朝左,数据中不会出现0值,也不会出现两只蚂蚁占用同一位置。其中,第一个数据代表的蚂蚁感冒了。要求输出1个整数,表示最后感冒蚂蚁的数目。
例如,
输入:
3
5 -2 8
程序应输出: 1
再例如,输入:
5
-10 8 -20 12 25
程序应输出: 3
思路:本题看上去复杂,但千万不能“模拟实现”,否则将十分复杂。
首先对题中几个误导点更正:第一蚂蚁相碰就原路返回,单从结果上来看,两个健康的蚂蚁碰面和穿过结果相同,有一只患病相碰和穿过结果相同,因此按照穿过来计算更加简便。
根据题意并给出的例子,我们可以得知,当患病蚂蚁向右时,右边所有向左走的,一定患病,向右走的一定不患病;当患病蚂蚁向左时,左边所有向右走的一定患病,向左走的一定不患病。
第二步,当第一个患病的按上述规律让部分患病后,第二次患病的还会继续感染其碰到的蚂蚁,这时考虑就比较麻烦,我们可以从最后结果来看,当第一个患病蚂蚁向右时,右边如果有蚂蚁才会继续感染,否则就永远只有一个患病,而有的话,那么第一个患病蚂蚁的左边所有向右走的蚂蚁也会患病;同理可以知道向左走的结果。
import java.util.Scanner;
public class antCold {
public static int Ant(int[] data) {
if(data.length>=50||data.length<=1) {
return -1;
}
int L=0,R=0;
for(int i = 1;i<data.length;i++) {
if(data[i]<=-100 || data[i]>=100) {
return -1;
}
if(data[0] > 0) {
if(Math.abs(data[i]) > data[0] && data[i] < 0) {
R++;
}
if(R == 0) {
return 1;
}
if(Math.abs(data[i]) < data[0] && data[i] > 0) {
L++;
}
}
else {
if(Math.abs(data[i]) < Math.abs(data[0]) && data[i] > 0) {
L++;
}
if(L == 0) {
return 1;
}
if(Math.abs(data[i]) > Math.abs(data[0]) && data[i] < 0) {
R++;
}
}
}
return R + L + 1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] data = new int[n];
for(int i = 0;i<data.length;i++) {
data[i] = sc.nextInt();
}
System.out.println(Ant(data));
}
}