k个数中求任意k-1个数的最小公倍数(LCM)的最小值
描述:有k个正整数 任取其中k-1个数 找到一个他们的最小公倍数数N 求N的最小值
分析:
可以采用以下三种方法:
******************************
1) 方法一:(缺点:需对k个数做质因素分解)
基本想法是先对每个数做质因素分解,然后对k-1个数两两相互统计,找出它们对应质因子的最大个数,最后当所有k-1个数统计结束时,结果就是该质因子个 数的最大值,然后将这些质因子相乘(个数>=1,需要重复相乘),找出lcm最小值即可。举个例子:例如有三个数:
x = 12 = 2*2*3;
y = 45 = 3*3*5;
z = 36 = 2*2*3*3
这里k=3,如果取(x,y)这一组共2个数,因为x中的质因子2在y中没有,将它加入lcm的质因子,即lcm=(2*2); x中的质因子3在y中有两个,这时需要在lcm中记录两个3而不是一个3,即lcm=(2*2*3*3); 最后y中的质因子5在x中没有,将它加入lcm的质因子,即lcm=(2*2*3*3*5) = 180。其他两组k-1个数同样处理,最后取最小的lcm就可以得出结果。
对这个例子程序运行结果为:
Input numbers: 12 45 36
(w/o 12), LCM Factors: 2 2 3 2 5 1 lcm: 180
(w/o 45), LCM Factors: 2 2 3 2 lcm: 36
(w/o 36), LCM Factors: 2 2 3 2 5 1 lcm: 180
N: 36
该解法参见下面程序中的solve()函数。
******************************
2)方法二:(优点:无需对k个数做质因素分解;缺点:需k-1个数 --- 共有k组 --- 单独计算)
由于lcm(a,b,c) = lcm(lcm(a,b),c);而且lcm(a,b)=a*b/gcd(a,b)
其中lcm表示最小公倍数,gcd表示最大公约数。
利用欧几里得算法,可以求得gcd(a,b)。
该解法参见下面程序中的solve2()函数。
******************************
3)方法三:(第二种方法的变体,原理是利用被剔除的元素x做文章)
由于要求k各元素中的k-1个数,考虑能否减少一些计算,首先计算全部元素的最小公倍数lcm(0,1,....k),然后考虑如何计算下式中的lcm(0,1,...k-1):
lcm(lcm(0,1,...k-1),x) = lcm(0,1,....k)
这样,问题转化为在lcm(0,1,....k)和x(这里x为要剔除的某个元素,共有k个这样的元素)已知的情况下,如何求上式中的lcm(0,1,...k-1)?
考虑下图一个实际例子,
假设lcm(0,1,...k)的值为变量lcm,即第一个式子,显然lcm一定能被第二式的x整除(因为lcm是k个数的最小公倍数)。这样x总可以表
示成(2):这里(6)式表示lcm中因子2和5的最大指数来自x;lcm中另外两个因子3和7的最大指数来一定来自lcm(0,1,...k-1),换
句话说,lcm(0,1,...k-1)里面一定有一个因子为(4)式(记作变量contriFactors),最后lcm(0,1,...k-1)可以
写成(7)式的形式,即lcm(0,1,...k-1)中因子2的最高指数不超过3,5的最高指数不超过2。
这样问题分解为两部分:1)如何在已知lcm和x的条件下,得出contriFactors的值;2)如何找出lcm(0,1,...k-1)中2和5的最高指数。
第一个部分可以这样计算:由于有条件式(3),将lcm/x得出第(5)式temp的值,这里a和b都大于0,且a=u-u'和b=v-v'。然后不断通
过 gcd(temp,x)可以消除x中的因子3和7,即可得到x的伴随变量y。将lcm/y就是第(7)式中左边部分contriFactors的值。
第二部分等价于k-1个数中剔除因子3和7后(得到的数中只含因子2和5),求它们的最小公倍数(记作变量lcmSub)就可得到。由于剔除因子3和7后,得到的k-1个新数比原来的数要小一些,计算它们的最小公倍数会相对容易一些。
最后将contriFactors乘上第二部分中的最小公倍数lcmSub就是lcm(0,1,...k-1)的值。
值得补充说明的是,如果y=1(即x中没有对lcm的贡献因子),那么(7)式(=lcm)一定是最大值,所以将y=1的情况剔除。也可以将这个方法只用
来判断y的值是否为1,如果不为1,仍然按照第二种方法计算k-1个数的最小公倍数。下面solve3()中就是只利用y做判断。(计算第二部分中的最小
公倍数lcmSub的代码已被注释掉)
这个解法的的效率高低取决于如何剔除公共因子(即下面的函数removeCommonFactors())。比较遗憾的是:测试证明,其效率比第二种方法
要稍微慢一点,原因可能是y=1的数据不多(即对lcm做出贡献因子的数据总体上占的比例较多,对这些数据而言计算y的值等于是多余的操作)。不过比第一
种分解质因子的方法要快很多。
该解法参见下面程序中的solve3()函数。
程序:
import java.lang.reflect.Array; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; /** * * @author ljs * 2011-06-23 * */ public class K_LCM { private static final boolean debug = false; //factors is returned as factor-count pair,e.g. //n=2*2*2*2*3*5: returns [2,4,3,1,5,1] (factors are in asc. order) private static LinkedList<Integer> findFactors(int n){ LinkedList<Integer> factorList = new LinkedList<Integer>(); int p = n; for(int i=2;i<=p;i++){ int count = 0; while(n % i == 0){ count++; n /= i; } if(count>0){ factorList.add(i); factorList.add(count); } } return factorList; } private static Map<Integer,LinkedList<Integer>> factorAllNumbers(int[] nums){ Map<Integer,LinkedList<Integer>> factoredNumsMap =new HashMap<Integer,LinkedList<Integer>>(); for(int i=0;i<nums.length;i++){ //no need to calculate the factors for duplicate numbers if(factoredNumsMap.get(nums[i]) == null){ LinkedList<Integer> currFactors = K_LCM.findFactors(nums[i]); factoredNumsMap.put(nums[i],currFactors); } } return factoredNumsMap; } private static LinkedList<Integer> findTwoNumbersLCM(LinkedList<Integer> prev,LinkedList<Integer> next){ int j=next.size()-1; //next list's position LinkedList<Integer> curr = new LinkedList<Integer>(prev); if(curr.size()==0){ //e.g. 1's factors list is empty curr.addAll(next); } for(int i=curr.size()-1;i>=0;i-=2){ int count = curr.get(i); int factor = curr.get(i-1); while(j>=0){ int jcount = next.get(j); int jfactor = next.get(j-1); if(jfactor == factor){ if(jcount>count){ //update the factor's count curr.set(i, jcount); } j-=2; break; }else { if(jfactor > factor){ //insert int insertAt = i + 1; if(insertAt<curr.size()){ curr.add(insertAt,jcount); curr.add(insertAt,jfactor); }else{ curr.addLast(jfactor); curr.addLast(jcount); } j-=2; //don't move i; }else { //add as a first element if(i-1==0){ curr.addFirst(jcount); curr.addFirst(jfactor); j-=2; //i has moved to head position }else{ //don't move j break; } } } } } //insert the least factors at the front while(j>=0){ int jcount = next.get(j); int jfactor = next.get(j-1); curr.addFirst(jcount); curr.addFirst(jfactor); j-=2; } return curr; } private static long findLCM(LinkedList<Integer>[] factorsList){ LinkedList<Integer> currFactors = factorsList[0]; for(int i=1;i<factorsList.length;i++){ LinkedList<Integer> nextFactors = factorsList[i]; currFactors = K_LCM.findTwoNumbersLCM(currFactors, nextFactors); } if(debug){ System.out.format("LCM Factors: "); for(int factor:currFactors) System.out.format(" %d",factor); } //caculate the lcm value long lcm = 1; for(int i=0;i<currFactors.size();i+=2){ int factor = currFactors.get(i); int count = currFactors.get(i+1); lcm *= (int)Math.pow(factor, count); } if(debug){ System.out.format(" lcm: %d",lcm); System.out.println(); } return lcm; } //find the minimum lcm among k-1 numbers (k=nums.length) @SuppressWarnings("unchecked") public static long solve(int[] nums){ if(debug){ System.out.format("Using method 1 - Input numbers: "); for(int num:nums) System.out.format("%d,",num); System.out.println(); } Map<Integer,LinkedList<Integer>> factoredNumsMap =K_LCM.factorAllNumbers(nums); int k =nums.length; LinkedList<Integer>[] factorsArr = (LinkedList<Integer>[])Array.newInstance(LinkedList.class,k-1); long lcmMin = Long.MAX_VALUE; for(int i=0;i<k;i++){ for(int j=0,m=0;j<k;j++){ if(j!=i){ factorsArr[m++] = factoredNumsMap.get(nums[j]); } } if(debug){ System.out.format("(w/o %d), ",nums[i]); } long lcm = K_LCM.findLCM(factorsArr); if(lcm<lcmMin) lcmMin = lcm; } if(debug){ System.out.format("%nN=%d%n",lcmMin); } return lcmMin; } public static long gcd(long m,long n){ m = (m<0)?-m:m; n = (n<0)?-n:n; while(n!=0){ long remainder = m%n; m = n; n = remainder; } return m; } public static long solve2(int[] nums){ if(debug){ System.out.format("Using method 2 - Input numbers: "); for(int num:nums) System.out.format("%d,",num); System.out.println(); } int k = nums.length; long lcmMin = Long.MAX_VALUE; for(int i=0;i<k;i++){ long lcm = 1; for(int j=0;j<k;j++){ if(j!=i){ lcm = lcm/gcd(lcm,nums[j])*nums[j]; } } if(debug) System.out.format("(w/o %d), LCM=%d%n",nums[i],lcm); if(lcm<lcmMin) lcmMin = lcm; } if(debug){ System.out.format("%nN=%d%n",lcmMin); } return lcmMin; } private static long lcm(int[] nums){ //calculate the lcm of the n numbers long lcm = 1; for(int j=0;j<nums.length;j++){ lcm = lcm/gcd(lcm,nums[j])*nums[j]; } return lcm; } //e.g. a=2^u*3^v*5^w; b=3^r*5^s*7^t, then return: //b=7^t private static long removeCommonFactors(long a,long b){ long temp = a; long gcd = gcd(temp,b); while(gcd != 1){ b/=gcd; temp = gcd; gcd = gcd(gcd,b); } return b; } //Another version: based on solve2(); no factoring public static long solve3(int[] nums){ if(debug){ System.out.format("Using method 3 - Input numbers: "); for(int num:nums) System.out.format("%d,",num); System.out.println(); } int k = nums.length; long lcmMin = Long.MAX_VALUE; long lcm = lcm(nums); for(int i=0;i<k;i++){ int x = nums[i]; long temp = lcm / x; long y = K_LCM.removeCommonFactors(temp,x); if(y==1) continue; /* long contriFactors = lcm/y; int[] newNums = new int[k-1]; for(int j=0,m=0;j<k;j++){ if(j!=i){ newNums[m++] = (int)K_LCM.removeCommonFactors(temp,nums[j]); } } long lcmSub = lcm(newNums); long m = lcmSub * contriFactors; if(debug) System.out.format("(w/o %d), LCM=%d%n",x,m); if(m<lcmMin) lcmMin = m; */ long lcmK_1 = 1; for(int j=0;j<k;j++){ if(j!=i){ lcmK_1 = lcmK_1/gcd(lcmK_1,nums[j])*nums[j]; } } if(debug) System.out.format("(w/o %d), LCM=%d%n",x,lcmK_1); if(lcmK_1<lcmMin) lcmMin = lcmK_1; } if(debug){ System.out.format("%nN=%d%n",lcmMin); } return lcmMin; } public static int[] generateRandIntegers(){ int totalKeys = 10; int KEYRANGE = 100; int i=0; int[] nums = new int[totalKeys]; while(i<totalKeys){ nums[i++] = (int)(Math.random()*KEYRANGE+1); } return nums; } public static void main(String[] args) { int[] inputNums = new int[]{2*2*2,2*3*3*3,2*3*5,3*3}; long N= K_LCM.solve3(inputNums); long N0= K_LCM.solve2(inputNums); if(N != N0){ System.out.println("Test Failed!"); } System.out.println("***************************************"); inputNums = new int[]{9,10,15}; N= K_LCM.solve3(inputNums); N0= K_LCM.solve2(inputNums); if(N != N0){ System.out.println("Test Failed!"); } System.out.println("***************************************"); inputNums = new int[]{80,40,79,7,13,39,32,80,74,4}; N= K_LCM.solve3(inputNums); N0= K_LCM.solve2(inputNums); if(N != N0){ System.out.println("Test Failed!"); } System.out.println("***************************************"); int testRandCases = 10000; int[][] cases = new int[testRandCases][]; for(int i=0;i<testRandCases;i++){ int[] nums = generateRandIntegers(); cases[i] = nums; } for(int i=0;i<testRandCases;i++){ int[] nums = cases[i]; long N1= K_LCM.solve(nums); long N2= K_LCM.solve2(nums); long N3= K_LCM.solve3(nums); if(!(N1 == N2 && N2 == N3)){ System.out.format("Input numbers: "); for(int num:nums) System.out.format("%d,",num); System.out.println(); System.out.format(" Test Failed!"); System.out.println(); System.out.println(); } } System.out.println("***************************************"); long start = System.currentTimeMillis(); for(int i=0;i<testRandCases;i++){ int[] nums = cases[i]; N= K_LCM.solve(nums); } long end = System.currentTimeMillis(); System.out.format("Using method 1 - time cost: %d%n", end-start); System.out.println("***************************************"); start = System.currentTimeMillis(); for(int i=0;i<testRandCases;i++){ int[] nums = cases[i]; N= K_LCM.solve2(nums); } end = System.currentTimeMillis(); System.out.format("Using method 2 - time cost: %d%n", end-start); System.out.println("***************************************"); start = System.currentTimeMillis(); for(int i=0;i<testRandCases;i++){ int[] nums = cases[i]; N= K_LCM.solve3(nums); } end = System.currentTimeMillis(); System.out.format("Using method 3 - time cost: %d%n", end-start); System.out.println("----END----"); } }
***************************************
Using method 1 - time cost: 1913
***************************************
Using method 2 - time cost: 350
***************************************
Using method 3 - time cost: 421
----END----
以下是使用这三种方法的一个实例测试结果(输入数据为9,10和15):
Using method 1 - Input numbers: 9,10,15,
(w/o 9), LCM Factors: 2 1 3 1 5 1 lcm: 30
(w/o 10), LCM Factors: 3 2 5 1 lcm: 45
(w/o 15), LCM Factors: 2 1 3 2 5 1 lcm: 90
N=30
Using method 2 - Input numbers: 9,10,15,
(w/o 9), LCM=30
(w/o 10), LCM=45
(w/o 15), LCM=90
N=30
Using method 3 - Input numbers: 9,10,15,
(w/o 9), LCM=30
(w/o 10), LCM=45
(w/o 15), LCM=90
N=30