数位全排列算法(可重复)之求和:非递归求法

问题描述:任意给定一个5位以内的整数(其中这个数字各个数位都不含有0,但是允许数位上的数字重复),然后对这个数字的各个数位的数字任意颠倒,成为新的数字组合,然后对这个组合里面的数字进行求和。

举个例子说明一下:

假如给定数字123,那么组合里面的数字可以是,132,213,231,312,321,123

再例如,233,那么组合里面的数字就可以是,233,323,332

再例如,222,那么组合里面就只有222

难点分析:

Kaiwii在解决这个问题的时候,觉得最棘手的主要有以下两个问题:

1、因为数位上的数字允许重复,而在新组成的组合中不允许数字重复,所以消重是一个比较麻烦的问题

2、有一种想法是将提取出来的数位上的数字重新编排成新的数字组合然后对他们求和。但是,我觉得这样子做太低效了,应该采取一些数学知识,通过不直接生成新数字的方式产生结果

下面就如何解决这两个棘手的问题,谈谈我的做法吧。

1、用一个数组记录数字中的各个数位上的数字

/**
 * Fetch each digital's value of the number,input and set them
 * respectively into the int []var
 * @param input
 * @return
 * Kaiwii
 */
	public boolean setvar(int input){
		boolean ret=false;
		/**
		 * initialize the int []var
		 * Kaiwii
		 */
		var=new int[5];
		for(int i=0;i<=var.length-1;i++){//because the variable,input is just a temporary variable
			//So,donn't be afraid it will be polluted 
			var[i]=input%10;//via arithmetical compliment,u can always get the last digit of the num
			if(var[i]!=0)setdigital();//zero is not acceptable,record how many digit the num have
			input/=10;//delete the last digit one by one
		}
		if(var!=null)ret=true;
		return ret;
	}

2、构造一个二维数组int [][]classify。第一维用来记录数值,第二维记录这个数值在各个数位中出现的次数

可能看到这里,你可能会有一个疑问,为什么不在第一个步骤中完成这项工作呢?Kaiwii认为,如果在第一个步骤中完成这项工作时间复杂度应该会比较大。试想一下,在探测到

某个数位的时候,因为你不知道全部数位的情况,你可能还需要通过遍历来搞清楚到底当前数位上的值是不是已经出现过了。这样子,你可能为整数1-9,设值9位大小的数组来记录数字出现的情况,还要每次探测一个数位都要遍历一边这个9位数组。可怕啊……所以,为了不趟这个浑水,Kaiwii采用下面的办法:

1、在探测各个数位的时候,Kaiwii会通过将某个数位修改为0来表示这个数位已经被探测过而且已经记录这个数值的出现个数。

2、在探测的过程中,只需要从左到右进行即可。因为,数位被遍历过就必定已经被处理了不需要重复操作。

	/**
	 * via analysis of the int []var containing the value of each digit,
	 * let int [][] classify containing different value of the digit in the first dimension
	 * and the times of the appearance of this value
	 * @return
	 * Kaiwii
	 */
	public boolean setclassify(){
		boolean ret=false;
		classify=new int[5][2];
		int zero=0;
		/**
		 * here is general idea:
		 * 0 is a very tricky num here.
		 * Because 0 is not acceptable in the input num and nonsense in the array,var.Whenever a num
		 * is set as zero,it will be neglected in the further detection.
		 * @author KaiwingHo
		 */
		for(int i=0;i<=var.length-1&&zero<=var.length;i++){//when every digit is set as zero,it means
			                                               //that the loop is over
			int temp;
			temp=var[i];
			if(temp==0)continue;//zero may exist in the array,but is not the digit value,so we need to
			                    //step over
			classify[i][0]=temp;//record the value
			classify[i][1]=1;//because the loop will never take back,so when a new number,not zero, is detected, 
			                 //it is the first appearance of this num
			var[i]=0;//0 represent the digit has been detected and recorded
			zero++;
			for(int j=i+1;j<=var.length-1;j++){//find out the afterward digit sharing the same value and record the times of the appearance
				if(var[j]==temp){
					var[j]=0;
					zero++;
					classify[i][1]++;//record the times
				}
			}
		}
		if(classify!=null&&var!=null)ret=true;
		return ret;
	}

3、运用数学知识求和

算法模型分析:

首先,我们先来考虑一下这样子一个问题,一个数字的大小问题。一个数字的大小,其实由两个部分组成,一个是数位的权值,另一个是数位上的数字。这样子说,可能比较抽象,我们考虑一下这样一个例子。比如说,数字124,我只需要知道百位的权值是100,十位的权值是10,个位的权值是1,而百位上的数字是1,十位上的数字是2,个位上的数字是4。那么,我们就可以知道数字124的大小事1x100+2x10+4x1。

然后,我们再来考虑一下以下一个问题。对于某个数字中的某个数位上的数字,出现新的排列组合中的数字中的某个数位上的次数是一样的。举个例子,数字233中数字2。他出现在新的排列组合中的数字的百位,十位,个位上的次数是一样的。

但是,Kaiwii又发现每个不同数值的数字出现在每个数位上的次数又是各不相同的。比如说,上面例子中的3和2出现的次数就不一样。

所以,Kaiwii就有一个想法。假如我们将某个数字出现的次数求出来,然后再乘以他们出现在每个数位上的权值,不就可以将这个数字对于排列组合求和中的“贡献”求出来了。比如有一个数字ABCC……(A、B、C分别表示数字的大小;这个数字的数位共有N个),然后我们需要求出这个数字的排列组合的和。我们就可以求出A出现的次数Na,B出现的次数Nb,C出现的次数Nc。A的贡献SUMa为NaXAX(1111……),其中1的个数为N。B、C的贡献SUMb、SUMc也可以通过同样的办法求出来。最后将SUMa、SUMb、SUMc加起来,那不就是这个排列组合之和么?

所以,接下来的工作就是求出各种数值的数字出现在各个数位上的次数求出来。而这个次数又与这个数字在初始数字中出现在各个数位上的次数有关。举个例子,比如初始数字是ABCCC…,通过分析可以知道A、B、C的次数分别为Ta,Tb,Tc,而这个数字总共的数位为N。那么A出现在排列组合中的某个数位上的可能次数为(n-1)!/[(Ta-1)!Tb!Tc!],而B的为(n-1)!/[(Tb-1)!Ta!Tc!],而C的为(n-1)!/[(Tc-1)!Ta!Tb!]。

注意到虽然每个不同数值的数字出现的次数是不一样的,但是,他们都有一个特点,他们都有一个共同的相似值(n-1)!/[Tc!Ta!Tb!]。所以,为了提高效率,我们可以先求出这个共同值,然后再特定化。比如对于A来说,就可以通过对这个共同值乘以Ta就可以了。

好吧,理论部分就说这么多,下面就是说说具体的实现吧。

3-1:

因为算法中需要使用对n求阶乘,所以就弱弱地实现了一个。如下所示:

/**
 * factorial for n
 * @param n
 * @return
 * Kaiwii
 */
	public static int factorial(int n){
		int ret=1;
		for(int i=2;i<=n;i++){
			ret*=i;
		}
		return ret;
	}

3-2:

已知数字的位数,求解每个数位的权值之和

/**
		 * get the sum of each digit's weight.And set it as the value of var2
		 */
		int var1=10;
		for(int i=1;i<=digital-1;i++){
			var1*=10;
		}
		int var2=var1-1;
		var2/=9;

3-3:

按照上面的算法思想求出每个数字出现排列组合中的数字的各个数位的次数,并且用这个次数乘以每个数位的权值之和,再乘以这个数字。最后将这些值加起来获得这个排列组合的总和ret

/**
		 * caculate the rounding probability of the digital
		 */
		int prob=Maths.factorial(digital-1);
		/**
		 * refine
		 */
		int divided=1;
		for(int[]temp:classify){
			if(temp[0]==0)continue;
			divided*=Maths.factorial(temp[1]);
		}
		for(int[]temp:classify){
			if(temp[0]==0)continue;
			int cur_divided=divided;
			cur_divided/=temp[1];
			int cur_probability;
			cur_probability=prob/cur_divided;
			ret+=var2*temp[0]*cur_probability;
		}	
		

最后来一组数据分别测试一下吧:

比如说,数字23456(每个数位上的数字都不一样的)

可以获得这样子的console结果:

num:6,times:1
num:5,times:1
num:4,times:1
num:3,times:1
num:2,times:1
sum:5333280

又比如说,数字34432(有重复的)

可以获得这样子的console结果:

num:2,times:1
num:3,times:2
num:4,times:2
num:0,times:0
num:0,times:0
sum:1066656

最后再举一个例子,88888(全部重复)

可以获得这样子的console结果:

num:8,times:5
num:0,times:0
num:0,times:0
num:0,times:0
num:0,times:0
sum:88888

总结:

适当运用数学知识,的确可以提高效率。因为之前在其他博客看过一个哥们在解决这个问题的时候,使用了一个递归的办法,不过这样子的效率肯定没有Kaiwii的这种来得高吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值