花朵数算法--java

 

这个问题原本来自国信蓝点的2011模拟题:

一个N位的十进制正整数,如果它的每个位上的数字的N次方的和等于这个数本身,则称其为花朵数。

例如:

当N=3时,153就满足条件,因为 1^3 + 5^3 + 3^3 = 153,这样的数字也被称为水仙花数(其中,“^”表示乘方,5^3表示5的3次方,也就是立方)。

当N=4时,1634满足条件,因为 1^4 + 6^4 + 3^4 + 4^4 = 1634。

当N=5时,92727满足条件。

实际上,对N的每个取值,可能有多个数字满足条件。

 

程序的任务是:求N=21时,所有满足条件的花朵数。注意:这个整数有21位,它的各个位数字的21次方之和正好等于这个数本身。

如果满足条件的数字不只有一个,请从小到大输出所有符合条件的数字,每个数字占一行。因为这个数字很大,请注意解法时间上的可行性。要求程序在3分钟内运行完毕。

在网上找到个非常好的java代码,读完记之!

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 NarcissusNumber {
    /**
     * 记录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 < 40; i++) {
        System.out.println(i+"位水仙花的结果:");
        NarcissusNumber narcissusNumber = new NarcissusNumber(i);
        narcissusNumber.show();
         }
        time = System.nanoTime() - time;
        System.out.println("time:\t" + time / 1000000000.0 + "s");
    }

    // 初始化计算时使用的数据结构,这也是提高效率的地方
    /**
     * @param n
     *            水仙花数的位数
     */
    public NarcissusNumber(int n) {
        PowerOf10 = new BigInteger[n + 1];
        PowerOf10[0] = BigInteger.ONE;
        Length = n;
        
        //初始化PowerPowerOf10
        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];
        
        //PreTable[i][j]  0-i的N次方出现0-j次的值
        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
    }

    /**
     * 检查sum是否是花朵数
     * @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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值