0出现的总次数__,1出现的总次数__
2出现的总次数__,3出现的总次数__
4出现的总次数__,5出现的总次数__
6出现的总次数__,7出现的总次数__
8出现的总次数__,9出现的总次数__
最初的分析,就是要得到一个长度为10的数组int[],统计10个元素出现的次数后,数字i出现的次数+1正好等于int[i]的数值。比如数组10个元素中数字1出现了5次,那么int[1]的值为6(因为题目中红色数字0~9各出现了一次)。所以,要解决的问题就是,要怎么去寻找符合要求的长度10数组。
1、最简单粗暴的方法就是穷举法。我刚开始也是用的穷举,看看有没有解再说,顺便看看到底要花多少时间。创建两个长度为10的数组num[]和com[],不难分析出,数组的元素至少是1,所以数组中元素的初值从1而不是0开始。num数组存放穷举的元素,从[1]...[1]到[9]...[9](现在还只讨论不超过9的数)。com[]赋初值也是1,统计num中数字出现的次数,然后对比num和com,如果相同,num就满足要求,不同就继续穷举。
用穷举法结果是得到了,num[]元素分别为1,7,3,2,1,1,1,2,1,1,但是时间花费非常久,处理器2.70Ghz的环境下,统计了10次平均时间179s。
2、逐次修改的方法。试想num[]赋了初值的数组,与统计数组com[]比较,比较到不同的元素就把num[]中不匹配的元素修改成正确的,比如统计num[]中数字1出现了5次,而num[1]=6,这时就修改num[1]=5,反复改过程,直到所有元素都正确。
下面贴出第一版的代码(会内存溢出):
static int num[];//存储数字的数组
static int wrongIndex;//检测到错误的index
static int rightNum;//错误index对应的正确数字
public int[] searchArray(){
if(rightNum!=0){//只有第一次运行时,rNum==0,故第一次不改变num的值
num[wrongIndex] = rightNum;
}
boolean result = countArrayAndCom(num);
if(!result){
searchArray();
}
return num;
}
//统计int数组中数字的个数,并判断num数组是否匹配,
//匹配则返回true,否则返回false
public boolean countArrayAndCom(int num[]){
//创建长度为10的数组,用于存放统计结果
int[] com = new int[10];
//赋给数组初值1
for(int i=0;i<com.length;i++){
com[i]=1;
}
for(int i=0;i<num.length;i++){
//num[i]分成百位,十位,个位再统计个数,如101→1,0,1
int[] digit = getSingleDigit(num[i]);
if(null!=digit){
for(int j=0;j<digit.length;j++){
switch(digit[j]){
case 0:com[0]++;break;
case 1:com[1]++;break;
case 2:com[2]++;break;
case 3:com[3]++;break;
case 4:com[4]++;break;
case 5:com[5]++;break;
case 6:com[6]++;break;
case 7:com[7]++;break;
case 8:com[8]++;break;
case 9:com[9]++;break;
default:System.out.println("Error");break;
}//switch
}//for digit
}//if digit
}
//比较num和com数组
return compareArray(num,com);
}
//比较两个数组是否相同
public boolean compareArray(int[] num,int[] com){
if(num.length!=com.length)
return false;
for(int i=0;i<num.length;i++){
if(num[i]!=com[i]){
wrongIndex=i;
rightNum=com[i];
return false;
}//if num[i]!=com[i]
if(num[num.length-i-1]!=com[num.length-i-1]){
wrongIndex = num.length-i-1;
rightNum = com[num.length-i-1];
return false;
}//if num.length-1
}//for num.length
return true;
}
//123→int[1][2][3]
public int[] getSingleDigit(int num){
if(num>=0&&num<=9){
int[] result = new int[1];
result[0]=num;
return result;
}else if(num>9){
String strDigit = num+"";
int digit = strDigit.length();
int[] result = new int[digit];
for(int i=0;i<result.length;i++){
result[i]= Integer.parseInt(strDigit.substring(i, i+1));
}
return result;
}else{
System.out.println("Error!");
}
return null;
}
需要说明的几个问题,countArrayAndCom(int[] num)方法调用compareArray(int[] num,int[] com)和getSingleDigit(int num)方法,检测num数组是否满足要求,getSingleDigit(int num)解决多位数问题,比如num[1]=11,那么就返回长度为2的int数组,两个元素都是1,然后再统计,就能统计2个1。
关键的方法是searchArray(),修改num数组中错误的元素,然后检测修改后的num数组是否满足要求,再递归调用,直到出现满足要求的num[]数组。
然后用main函数调用
public static void main(String[] args){
num = new int[10];
//赋给初值
for(int i=0;i<num.length;i++)
num[i]=1;
long start = System.currentTimeMillis();
CountNumII cn = new CountNumII();
num = cn.searchArray();
long end = System.currentTimeMillis();
System.out.println("用时:"+(end-start)+"毫秒");
if(null!=num){
for(int i=0;i<num.length;i++)
System.out.println(" num[i]是:"+num[i]);
}else{System.out.println("没有结果"); }
}
这样的方法看似还不错,但是运行之后报错,得不到结果,内存溢出,抛出异常如下:
Exception in thread "main" java.lang.StackOverflowError
打印num数组的变化过程,发现searchArray()方法陷入了死循环,递归怎么都跳不出来。num数组一直循环{1,12,1,1,1,1,1,1,1,1}→{1,11,1,1,1,1,1,1,1,1}→{1,12,1,1,1,1,1,1,1,1}。
看着上述死循环,突然发现如果{1,11,1,1,1,1,1,1,1}从num[9]→num[0]的顺序寻找不匹配,就会把num[2]的1换成2,而不是把num[1]的11换成12,即出现{1,11,2,1,1,1,1,1,1,1}。这不正是满足要求的一组数吗?所以就考虑比对num和com数组时,不要每次都从num[0]→num[9]的顺序。
首先想的是num[0]→num[9]和num[9]→num[0]依次比较,但仍然是死循环,只是循环的组合成员变多了(具体情况和不详细讲了,与上述情况类似)。
进一步就想到比较顺序随机选定,代码只修改了compareArray(int[] num,int[] com)方法,
//比较两个数组是否相同
public boolean compareArray(int[] num,int[] com){
if(num.length!=com.length)
return false;
int randomflag = (new Random()).nextInt(2);
for(int i=0;i<num.length;i++){
if(randomflag%2==0){
if(num[i]!=com[i]){
wrongIndex=i;
rightNum=com[i];
return false;
}//if num[i]!=com[i]
}else{
if(num[num.length-i-1]!=com[num.length-i-1]){
wrongIndex = num.length-i-1;
rightNum = com[num.length-i-1];
return false;
}//if num.length-1
}//if else random%2
}//for num.length
return true;
}
虽然if,for语句有点多,影响阅读,仔细看还是挺清晰的。经此修改后,再运行主函数,就能得到结果,num数组是{1,7,3,2,1,1,1,2,1,1},满足要求。但用时经常是0毫秒,偶尔1毫秒,是毫秒不是秒。
速度如此之快还有一种可能就是,赋给num初值全部是1,离结果比较近。那么给num赋初值全部是9,计算时间就稳定到了1毫秒(初值为1时,运行时间经常为0毫秒,偶尔是1毫秒,上图是截取1毫秒的情况)。就效率来说,相比穷举法,有了极大幅度的提高。
但是还有一个没解决的问题,前文提到组合{1,11,2,1,1,1,1,1,1}也是一组解,就说明存在多解。用上述方法总是收敛到{1,7,3,2,1,1,1,2,1,1}。mai函数中给num随机赋初值也是同样的情况。所以怎样得到更完整的解,还需要继续思考。
思考这个问题只是兴趣使然,觉得有点意思再加上坐车比较无聊就拿来想了一番,没有探究算法是否有意义,我想并不是所有的事情都该去深究其意义何在,感兴趣就想一想,仅此而已。