有甲、乙、丙、丁、戊、己、庚、辛...等 n 个人按顺序坐在一列椅子上,现在每个人都可以交换位置(不一定要交换),但交换半径只能为m。问一共有几种交换方法?(m << n)
说明:如果m为2,则丙只能坐到甲乙丁戊的位置,而丁只能坐到乙丙戊己的位置。
背景:
这几个礼拜的Java每周作业都是破解字符替换式密码,如破解:53$$+305))6*;4826)4$.)4$);806*;48+8%60))85;;]8*;:$*8+83(88)5*+;46(;88*96*?;8)*$(;485);5*+2:*$(;4956*2(5*-4)8%8*;4069285);)6+8)4$$;1($9;48081;8:8$1;48+85;4)485+528806*81($9;48;(88;4($?34;48)4$;161;:188;$?;
一种方法就是字母频率分析,还有一种是单词模式分析。
在做字母频率分析的时候,分析完了密码的字符频率和当代的英文书籍的字母频率后不知道怎么做,因为它们不能按照频率大小顺序简单地对应起来。但最大频率的字符肯定不会对应到字母"x"或"z"这种生僻字母,只会对应字母频率的第一大,第二大,第三大或第四大,越往后概率越低,直到变成小概率事件。
这里就想到了如果对应关系在一定的半径之内,列出所有的可能再进行下一步操作。
这就引出了一个问题上面的问题。
脑子热写了一个递归,没有严格证明,简单证明就是如果设置n = m,那么结果就是n个元素的全排列的所有情况的数目。
示例代码(用javadoc写的解释):
package testLand;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
/**
* @author Serious
* @version 1.0
*
* Sample input and output
*
* n: 14 m: 2
* answer: 64481
* num_of_operations: 582470767
* time: 123s
*
* n: 13 m: 2
* answer: 27495
* num_of_operations: 116486835
* time: 22s
*
* n: 12 m: 2
* answer: 11854
* num_of_operations: 23297053
* time: 4s
*
*n: 11 m: 2
* answer: 5080
* num_of_operations: 4665756
* time: 1s
*/
public class test {
private static int radius;
private static int count = 0;
private static HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
private static HashSet<ArrayList<Character>> set = new HashSet<ArrayList<Character>>();
public static void setR(int i) {
radius = i;
}
public static void main(String[] args) {
long begin = System.currentTimeMillis();
setR(2);
char[] testArr = {'a','b','c','d','e','f','g','h','i','j','k','l','m'};
System.out.println("n: "+testArr.length+" m: "+radius);
for(int i = 0 ; i <testArr.length ; i++)
hm.put(testArr[i], i);
ans(0, testArr);
System.out.println("answer: "+set.size());
System.out.println("num_of_operations: "+count);
long end = System.currentTimeMillis();
System.out.println("time: "+(end-begin)/1000+"s");
}
/**
* This method is for searching all combinations corresponding to the question
* Check all the periphery of the accessible area of the element——testArr[index], if
* the element in checking point is able to exchange, exchange testArr[index] and
* testArr[check]. After exchanging, put the new combination into a HashSet which can
* remove the duplicate items, and then, the recursion come to next layer by invoking
* the method itse: if(index!=testArr.length-1) ans(index+1, testArr)
* <P>
* @param index The index of present checking element in array
* @param testArr An array for searching the answer
*/
public static void ans(int index, char[] testArr) {
for(int check = index - radius; check <= index + radius; check++ ) {
if(check>-1 && check < testArr.length-1) {
if(check != index && hm.get(testArr[check])-radius <=index && index<=hm.get(testArr[check]) +radius
&& hm.get(testArr[index])-radius<=check && check<=hm.get(testArr[index])+radius) {
testArr = switchE(index, check, testArr);
char[] tmp = testArr;
for(int n = 0 ; n < testArr.length ; n++) {
char tmpc = testArr[n];
tmp[n] = tmpc;
}
ArrayList<Character> tmpArr = new ArrayList<Character>();
for(char c : testArr)
tmpArr.add(c);
count++;
set.add(tmpArr);
}
if(index != testArr.length-1) ans(index+1, testArr);
}
}
}
public static char[] switchE(int i, int j, char[] testArr) {
char temp = testArr[i];
char temp2 = testArr[j];
testArr[i] = temp2 ;
testArr[j] = temp;
return testArr;
}
}
进步的想法就是,先列出全排列,再逐一筛选符合要求的。