从1开始,我可以计算多少,当我可以使用任何数字最多N次

时间:2017-01-11 01:43:36

标签: java algorithm

我的问题如下;对于数字N,我需要找出我可以计算的最大值,每个数字可以使用N次。

例如,如果N = 5,则最大值为12,因为此时数字1已被使用了5次。

我最初的方法是简单地遍历所有数字,并记录到目前为止每个数字的使用次数。当N很大时,这显然是非常低效的,所以我正在寻找有关实现这一目标的更聪明(也更有效)方法的建议。

public class Counter {

    private static Hashtable<Integer, Integer> numbers;

    public static void main(String[] args){
        Counter c = new Counter();
        c.run(9);
    }

    public Counter() {
        numbers = new Hashtable<Integer, Integer>();

        numbers.put(0, 0);
        numbers.put(1, 0);
        numbers.put(2, 0);
        numbers.put(3, 0);
        numbers.put(4, 0);
        numbers.put(5, 0);
        numbers.put(6, 0);
        numbers.put(7, 0);
        numbers.put(8, 0);
        numbers.put(9, 0);

    }

    public static void run(int maxRepeat) {

        int keeper = 0;

        for(int maxFound = 0; maxFound <= maxRepeat; maxFound++) {
            keeper++;
            for (int i = 0; i < Integer.toString(keeper).length(); i++) {
                int a = Integer.toString(keeper).charAt(i);
                 //here update the tally for appropriate digit and check if max repeats is reached
                }
            }

        System.out.println(keeper);
    }
}

2 个答案:

答案 0 :(得分:6)

对于初学者而言,不是使用Counter支持Hashtable,而是使用int[]。当你确切地知道地图必须有多少元素时,特别是当数字是数字时,数组是完美的。

话虽这么说,我认为最有效的加速可能来自更好的数学,而不是更好的算法。通过一些实验(或者可能很明显),您会注意到1始终是第一个使用给定次数的数字。所以给定N,如果您能找到第一个使用数字1 N+1次的数字,您知道答案是之前的数字。这可以让你解决问题,而不必实际计算那么高。

现在,让我们看一下使用多少个1来计算各种数字。在这篇文章中,我将使用n指定一个数字,当我们试图找出有多少1用于计算数字时,而大写N指定多少1&# 39; s用于计算某些东西。

一位数字

从一位数字开头:

1:  1
2:  1
...
9:  1

显然,计算一位数字所需的1的数量是...... 1.好吧,实际上我们忘记了一个:

0:  0

稍后这将很重要。所以我们应该这样说:计算到一位数X所需的1的数量是X > 0 ? 1 : 0。让我们定义一个数学函数f(n),它将代表计算n&#34;所需的1个数字。然后

f(X) = X > 0 ? 1 : 0

两位数字

对于两位数字,有两种类型。对于1X

形式的数字
10: 2
11: 4
12: 5
...
19: 12

您可以这样想:计算到1X需要等于1的数字

  • f(9)(从最高计数到9)加上
  • 1(从10开始)加上
  • X(来自11的第一个数字 - 1X包括在内,如果X > 0)加上
  • 然而,要求很多1来计算X

或数学上,

f(1X) = f(9) + 1 + X + f(X)

然后有两位数字高于19:

21: 13
31: 14
...
91: 20

使用YX计算为两位数Y > 1所需的1的数量是

  • f(19)(从最多计算到19)加上
  • f(9) * (Y - 2)(从数字20到(Y-1)9的1&#39包括 - 如果Y = 5,我的意思是20-49中的1,来自从21,31,41)加上
  • 然而,要求很多1来计算X

或数学上,Y > 1

f(YX) = f(19) + f(9) * (Y - 2) + f(X)
      = f(9) + 1 + 9 + f(9) + f(9) * (Y - 2) + f(X)
      = 10 + f(9) * Y + f(X)

三位数

一旦你进入三位数的数字,你可以扩展模式。对于表格1YX(现在Y可以是任何内容)的任何三位数字,从计数到该数字的总计数将为

  • f(99)(从最高计数到99)加上
  • 1(从100)加上
  • 10 * Y + X(从101的最初数字 - 1YX包括在内)加上
  • 然而,要求许多1个人以两位数的数字计算YX

所以

f(1YX) = f(99) + 1 + YX + f(YX)

请注意f(1X)的并行。将逻辑继续到更多数字,对于以1开头的数字,模式是

f(1[m-digits]) = f(10^m - 1) + 1 + [m-digits] + f([m-digits])

[m-digits]表示长度为m的数字序列。

现在,对于不以1开头的三位数ZYX,即Z > 1,计算到它们所需的1位数是

  • f(199)(从最多计算到199)加上
  • f(99) * (Z - 2)(来自200的1 - (Z-1)99包括在内)
  • 然而,要求很多1来计算YX

所以

f(ZYX) = f(199) + f(99) * (Z - 2) + f(YX)
       = f(99) + 1 + 99 + f(99) + f(99) * (Z - 2) + f(YX)
       = 100 + f(99) * Z + f(YX)

现在,以1开头的数字模式似乎很清楚:

f(Z[m-digits]) = 10^m + f(10^m - 1) * Z + f([m-digits])

一般情况

我们可以将最后的结果与以1开头的数字的公式结合起来。您应该能够验证以下公式是否等同于上面给出的所有数字Z 1-9的适当情况,并且Z == 0

时它做对了
f(Z[m-digits]) = f(10^m - 1) * Z + f([m-digits])
                                + (Z > 1) ? 10^m : Z * ([m-digits] + 1)

对于10^m - 1形式的数字,如99,999等,您可以直接评估该函数:

f(10^m - 1) = m * 10^(m-1)

因为数字1将在每个10^(m-1)数字中使用m次 - 例如,当计数到999时,将使用100个1?数百&#39;地方,100 1&#39;用于数十&#39;这些地方使用了100个1&1;地点。所以这就变成了

f(Z[m-digits]) = Z * m * 10^(m-1) + f([m-digits])
                                  + (Z > 1) ? 10^m : Z * ([m-digits] + 1)

你可以修改确切的表达方式,但我认为这非常接近于它,无论如何这种特殊的方法。你在这里有一个递归关系,它允许你通过在每一步去掉一个前导数字来评估f(n),即计数到n所需的1的数量。其时间复杂度为n中的对数

实施

根据上面的最后一个公式,实现这个功能很简单。从技术上讲,你可以在递归中使用一个基本案例:空字符串,即将f("")定义为0.但它会为你节省一些调用以处理单个数字以及形式{{ 1}}。以下是我的工作方式,省略了一些参数验证:

10^m - 1

反相

这个算法解决了你要问的问题的反转:也就是说,它计算出一个数字的使用次数,计算到private static Pattern nines = Pattern.compile("9+"); /** Return 10^m for m=0,1,...,18 */ private long pow10(int m) { // implement with either pow(10, m) or a switch statement } public long f(String n) { int Z = Integer.parseInt(n.substring(0, 1)); int nlen = n.length(); if (nlen == 1) { return Z > 0 ? 1 : 0; } if (nines.matcher(n).matches()) { return nlen * pow10(nlen - 1); } String m_digits = n.substring(1); int m = nlen - 1; return Z * m * pow10(m - 1) + f_impl(m_digits) + (Z > 1 ? pow10(m) : Z * (Long.parseLong(m_digits) + 1)); } ,而你想知道哪个{{1}您可以使用给定数字n的数字(即1&#39; s)来达到。因此,正如我在开始时提到的那样,您正在寻找n的第一个N

最简单的方法是从n开始向上计数,看看何时超过f(n+1) > N

n = 0

但当然,与在数组中累积计数相比,并没有更好(实际上可能更糟)。 N的全部意义在于你不必测试每个数字;你可以跳过很长的间隔,直到找到public long howHigh(long N) { long n = 0; while (f(n+1) <= N) { n++; } return n; } f,然后使用跳转缩小你的搜索范围。我推荐的一个相当简单的方法是exponential search将结果置于上限,然后进行二分搜索以缩小范围:

n

由于上面f(n+1) > N的实现是O(log(n))而指数+二进制搜索也是O(log(n)),所以最终的算法应该是O(log ^ 2( n)),我认为N和n之间的关系足够线性,你也可以认为它是O(log ^ 2(N))。如果您在日志空间中搜索并明智地缓存该函数的计算值,则可能将其降低到大致为O(log(N))。在确定上限后,可能提供显着加速的变量会在interpolation search轮中出现,但要正确编码是很棘手的。完全优化搜索算法可能是另一个问题的问题。

答案 1 :(得分:1)

这应该更有效率。使用大小为10的整数数组来保持数字位数。

public static int getMaxNumber(int N) {
    int[] counts = new int[10];
    int number = 0;
    boolean limitReached = false;
    while (!limitReached) {
        number++;
        char[] digits = Integer.toString(number).toCharArray();
        for (char digit : digits) {
            int count = counts[digit - '0'];
            count++;
            counts[digit - '0'] = count;
            if (count >= N) {
                limitReached = true;
            }
        }
    }
    return number;
}

更新1:由于@Modus Tollens提到初始代码有错误。当N = 3返回11时,1之间有1 and 11count[i] > N。修复是检查给定数量是否违反限制i,应返回之前的数字。但是,对于其他count[i] == N j的某些count[j] <= N public static int getMaxNumber(int N) { int[] counts = new int[10]; int number = 0; while (true) { number++; char[] digits = Integer.toString(number).toCharArray(); boolean limitReached = false; for (char digit : digits) { int count = counts[digit - '0']; count++; counts[digit - '0'] = count; if (count == N) { //we should break loop if some count[i] equals to N limitReached = true; } else if (count > N) { //previous number should be returned immediately //, if current number gives more unique digits than N return number - 1; } } if (limitReached) { return number; } } } ,应返回实际的数字。

请参阅以下相应代码:

N=13

更新2:正如@David Z和@Modus Tollens所提到的那样,如果 public static int getMaxNumber(int N) { int[] counts = new int[10]; int number = 0; while (true) { number++; char[] digits = Integer.toString(number).toCharArray(); for (char digit : digits) { int count = counts[digit - '0']; count++; counts[digit - '0'] = count; if (count > N) { return number - 1; } } } } ,则应该返回30,即当N被破坏但未达到时,算法会停止。如果这是初始要求,代码将更简单:

conditional statement