Java应用之自幂数及优化

本文介绍了一种高效的自幂数求解算法,通过优化的数据结构和剪枝策略,显著提高了搜索自幂数的速度,特别适用于较大位数的自幂数求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      对于一个正整数而言,长度是n,如果它的各位上的数字的n次方之和正好等于它本身,那么我们称这样的数为自幂数.
      (例如:当n为3时,有1^3 + 5^3 + 3^3 = 153,153即是n为3时的一个自幂数)


     n为1时,自幂数称为独身数。
     n为2时,没有自幂数。
     n为3时,自幂数称为水仙花数。
     n为4时,自幂数称为玫瑰花数。
     n为5时,自幂数称为五角星数。
     n为6时,自幂数称为六合数。
     n为7时,自幂数称为北斗七星数。
     n为8时,自幂数称为八仙数。
     n为9时,自幂数称为九九重阳数。
     n为10时,自幂数称为十全十美数。

  

     基础版(最慢):
    下面的代码参考了http://www.oschina.net/code/snippet_1439376_33874

    

public class 自幂数 {

	public static void main(String[] args) {
		basicFun(7);
	}

	/**
	 * 限制n<=9,9以上速度慢的无法忍受
	 */
	public static void basicFun(int n) {
		switch (n) {
		case 1:
			System.out.println("独身数:");
			System.out.print("0" + "\t");
			break;
		case 2:
			System.out.println("两位自幂数:");
			System.out.println("没有自幂数!");
			break;
		case 3:
			System.out.println("水仙花数:");
			break;
		case 4:
			System.out.println("玫瑰花数:");
			break;
		case 5:
			System.out.println("五角星数:");
			break;
		case 6:
			System.out.println("六合数:");
			break;
		case 7:
			System.out.println("北斗七星数:");
			break;
		case 8:
			System.out.println("八仙数:");
			break;
		case 9:
			System.out.println("九九重阳数:");
			break;
		default:
			System.out.println("坐等屏蔽:");
			break;
		}
		if (n >= 10 || n <= 0) {
			System.out.println("----------------已屏蔽,程序结束----------------");
			return;
		}
		// 缓存结果
		Map<Integer, Long> cacheMap = new HashMap<Integer, Long>();
		for (int i = 0; i < 10; i++) {
			cacheMap.put(i, (long) Math.pow(i, n));
		}
		Map<Integer, Long> numCacheMap = new HashMap<Integer, Long>();
		for (int i = 0; i < n; i++) {
			numCacheMap.put(i, (long) Math.pow(10, i));
		}
		for (long number = (long) Math.pow(10, n - 1), len = (long) Math.pow(
				10, n); number < len; number++) {
			long num = 0;
			for (int i = 0; i < n; i++) {
				int temp = (int) (number / numCacheMap.get(i)) % 10;
				num += cacheMap.get(temp);
			}
			// 符合条件
			if (number == num) {
				System.out.print(number + "\t");
			}
		}
	}
}

 
     缺点如下:
     n=9运算非常慢,n=10基本没指望了,改进算法是尽早排除掉不符合条件的。

 

     网上的解决方案:
     参考讨论:http://bbs.youkuaiyun.com/topics/360185693 
     结论:理论上,最大的水仙花数不超过34位
     方法1:组合combination(n+9,n),然后一一检验是否满足条件;
     方法2:双向搜提前剪枝:根据高位提前剪枝,看是否要继续递归下去
   
     网上Java版的解决方案,使用了剪枝: http://bbs.youkuaiyun.com/topics/360188055#r_73147548

    

import java.math.BigInteger;

/**
 * 水仙花数:N位整数,它等于各位上的数字的N次方之和,例如有一个N位数字,a1a2a3a4.....aN = a1^N +a2^N+......aN^N
 * 
 * 算法原理: 注意:以下 sum 为各位上的数字的N次方之和 sum_a为集合a中的数的sum
 * 
 * 对于任意的N位数字,定义形如315,351,513等这样的数字都属于“1出现1次,3出现1次,5出现1次”的集合a
 * 明显可以看出“包含在集合a中的数的sum都相同”,即sum_a="1^N(位数)*T1(1出现的次数)+3^N*T3+5^N*T5",
 * 观察得,如果集合a包含水仙花数,则该水仙花数=水仙花数的sum(水仙花数定义)=sum_a(充要条件)。
 * 可以随便举个反例215,512,125在集合b中,但b的sum_a=134明显不属于集合b,所以b不是包含水仙花数的集合
 * 总结:将寻找水仙花数,转换为寻找包含水仙花数的集合,从而减少判断的次数。
 * 
 * 结束条件:(楼主在这里进行了优化) 首先不是一次选完,而是从0到N个9,0到N个8...这样选,总数由remainCount控制 设当前情况为集合a
 * 1.如果当sum_a大于最大数即10^N-1,矛盾 2.因最小的数字为10^(N -
 * 1),注意到,如果选某数时sum_a小于最小值,则后面的情况不用讨论了。
 * 例如3位数,已选1个3选2,发现sum_a最大为=3^3*1+2^3*2=43<100,可以断定不用选2,1,0了;
 * (当前情况能表示的最大数:比如说3位数我选了1个9,8的情况选完了不行,现在开始选7,最大数就是977不可能是987)
 * 3.判断sum_a和当前情况能表示的最大数首部相同部分中某数出现的次数是否比已经选择的集合中该数出现的次数多
 * 设sum_a=99900当前情况能表示的最大数为99988,则当前情况的数肯定在这之间即999XX,而当前情况9已经选了且只选了1次,则矛盾。
 * 4.同上:相同部分中还没选的数 的出现次数比剩余的总数还多 例如相同部分为789111,1还没选而且只剩2个数没选了,则矛盾
 * 5.当选完所有数时如果sum_a属于集合a,则sum_a为水仙花数。
 * 
 */
public class NarcissisticNumber {
	/**
	 * 记录10的0~N次方
	 */
	private static BigInteger[] PowerOf10;
	/**
	 * 记录0到9中任意数字i的N次方乘以i出现的次数j的结果(i^N*j)
	 */
	private static BigInteger[][] PreTable;
	/**
	 * 记录可能为水仙花数的下限与PreTable中对应数的差
	 */
	// private static BigInteger[][] PreTable2; 没什么用,变量定多了不容易理解
	/**
	 * 记录离PreTable中对应数最近的10的k次方
	 */
	private static int[][] PreTable3;
	/**
	 * 记录0到9中每个数出现的次数
	 */
	private static int[] Selected = new int[10];
	/**
	 * 记录水仙花数的位数
	 */
	private static int Length;
	/**
	 * 记录水仙花数出现的个数
	 */
	private static int Count = 0;
	/**
	 * 记录当前的进制
	 */
	private static int NumberSystem = 10;

	public static void main(String[] args) {
		long time = System.nanoTime();
		for (int i = 1; i < 11; i++) {
		NarcissisticNumber narcissisticNumber = new NarcissisticNumber(i);
		narcissisticNumber.show();
		}
		time = System.nanoTime() - time;
		System.out.println("time:\t" + time / 1000000000.0 + "s");
	}

	// 初始化计算时使用的数据结构,这也是提高效率的地方
	/**
	 * @param n
	 *            水仙花数的位数
	 */
	public NarcissisticNumber(int n) {
		PowerOf10 = new BigInteger[n + 1];
		PowerOf10[0] = BigInteger.ONE;
		Length = n;
	
		for (int i = 1; i <= n; i++){
			PowerOf10[i] = PowerOf10[i - 1].multiply(BigInteger.TEN);
		}
	
		PreTable = new BigInteger[NumberSystem][n + 1];
		// PreTable2 = new BigInteger[NumberSystem][n + 1];
		PreTable3 = new int[NumberSystem][n + 1];
	
		for (int i = 0; i < NumberSystem; i++) {
			for (int j = 0; j <= n; j++) {
				PreTable[i][j] = new BigInteger(new Integer(i).toString()).pow(
						n).multiply(new BigInteger(new Integer(j).toString()));
				// PreTable2[i][j] = PowerOf10[Length - 1]
				// .subtract(PreTable[i][j]);
	
				for (int k = n; k >= 0; k--) {
					if (PowerOf10[k].compareTo(PreTable[i][j]) < 0) {
						PreTable3[i][j] = k;
						break;
					}
				}
			}
		}
	}

	private void show() {
		Search(NumberSystem - 1, BigInteger.ZERO, Length);
	}

	/**
	 * @param currentIndex
	 *            记录当前正在选择的数字(0~9)
	 * @param sum
	 *            记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
	 * @param remainCount
	 *            记录还可选择多少数
	 */
	private static void Search(int currentIndex, BigInteger sum, int remainCount) {
		if (sum.compareTo(PowerOf10[Length]) >= 0)// 见结束条件1
		{
			return;
		}
	
		if (remainCount == 0) {// 没数可选时
			if (sum.compareTo(PowerOf10[Length - 1]) > 0 && Check(sum)) {// 见结束条件5
				Count++;
				System.out.print(Count + " ");
				System.out.println(sum);
			}
			return;
		}
	
		if (!PreCheck(currentIndex, sum, remainCount))// 见结束条件3,4
			return;
	
		if (sum.add(PreTable[currentIndex][remainCount]).compareTo(
				PowerOf10[Length - 1]) < 0)// 见结束条件2
			return;
	
		if (currentIndex == 0) {// 选到0这个数时的处理
			Selected[0] = remainCount;
			Search(-1, sum, 0);
		} else {
			for (int i = 0; i <= remainCount; i++) {// 穷举所选数可能出现的情况
				Selected[currentIndex] = i;
				Search(currentIndex - 1, sum.add(PreTable[currentIndex][i]),
						remainCount - i);
			}
		}
		// 到这里说明所选数currentIndex的所有情况都遍历了
		Selected[currentIndex] = 0;
	}

	/**
	 * @param currentIndex
	 *            记录当前正在选择的数字(0~9)
	 * @param sum
	 *            记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
	 * @param remainCount
	 *            记录还可选择多少数
	 * @return 如果当前值符合条件返回true
	 */
	private static Boolean PreCheck(int currentIndex, BigInteger sum,
			int remainCount) {
		if (sum.compareTo(PreTable[currentIndex][remainCount]) < 0)// 判断当前值是否小于PreTable中对应元素的值
			return true;// 说明还有很多数没选
	
		BigInteger max = sum.add(PreTable[currentIndex][remainCount]);// 当前情况的最大值
		max = max.divide(PowerOf10[PreTable3[currentIndex][remainCount]]);// 取前面一部分比较
		sum = sum.divide(PowerOf10[PreTable3[currentIndex][remainCount]]);
	
		while (!max.equals(sum)) {// 检验sum和max首部是否有相同的部分
			max = max.divide(BigInteger.TEN);
			sum = sum.divide(BigInteger.TEN);
		}
	
		if (max.equals(BigInteger.ZERO))// 无相同部分
			return true;
	
		int[] counter = GetCounter(max);
	
		for (int i = 9; i > currentIndex; i--)
			if (counter[i] > Selected[i])// 见结束条件3
				return false;
	
		for (int i = 0; i <= currentIndex; i++)
			remainCount -= counter[i];
	
		return remainCount >= 0;// 见结束条件4
	}

	/**
	 * @param sum
	 *            记录当前值(如选了3个9、2个8 就是9^N*3+8^N*2)
	 * @return 如果sum存在于所选集合中返回true
	 */
	private static Boolean Check(BigInteger sum) {
		int[] counter = GetCounter(sum);
	
		for (int i = 0; i < NumberSystem; i++) {
			if (Selected[i] != counter[i])
				return false;
		}
	
		return true;
	}

	/**
	 * @param value
	 *            需要检验的数
	 * @return 返回value中0到9出现的次数的集合
	 */
	public static int[] GetCounter(BigInteger value) {
		int[] counter = new int[NumberSystem];
		char[] sumChar = value.toString().toCharArray();

		for (int i = 0; i < sumChar.length; i++)
			counter[sumChar[i] - '0']++;

		return counter;
	}
}

 

    运行结果:

   

1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 153
10 370
11 371
12 407
13 1634
14 8208
15 9474
16 54748
17 92727
18 93084
19 548834
20 1741725
21 4210818
22 9800817
23 9926315
24 24678050
25 24678051
26 88593477
27 146511208
28 472335975
29 534494836
30 912985153
31 4679307774
time:	0.080869496s

 

   基本上是光速。佩服。

 

    欢迎各位朋友给出更快更优的方法,全文完。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值