暴力破解

暴力破解的实用性

暴力破解是大赛以及企业应用中常用的方法之一,往往大部分题目都可以适用于暴力破解,直接、准确,不在乎结构的美观,但是效率高,方法简单直接,易想到。

但可能时间复杂度和空间复杂度要更高。

例题讲解

真题:年龄谜题
美国数学家维纳(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));
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值