简体   繁体   English

打印非零数字按升序排列的所有数字

[英]Print all numbers whose nonzero digits are in ascending order

I'm trying to write a program which takes as arguments a number of digits and a base, and counts upward through the numbers that have their nonzero digits in ascending order. 我正在尝试编写一个程序,该程序将多个数字和一个基数作为参数,并通过按升序排列非零数字的数字向上计数。 For instance, in base 4 with 3 digits, it should print: 例如,在带有3位数的基数4中,它应该打印:

000 001 002 003 010 011 012 013 020 022 023 030 033 100 101 102 103 110 111 112 113 120 122 123 130 133 200 202 203 220 222 223 230 233 300 303 330 333 000 001 002 003 010 011 012 013 020 022 023 030 033 100 101 102 103 110 111 112 113 120 122 123 130 133 200 202 203 220 222 223 230 233 300 303 330 333

and in base 3 with 4 digits it should print: 在4位数的基数3中应该打印:

0000 0001 0002 0010 0011 0012 0020 0022 0100 0101 0102 0110 0111 0112 0120 0122 0200 0202 0220 0222 1000 1001 1002 1010 1011 1012 1020 1022 1100 1101 1102 1110 1111 1112 1120 1122 1200 1202 1220 1222 2000 2002 2020 2022 2200 2202 2220 2222 0000 0001 0002 0010 0011 0012 0020 0022 0100 0101 0102 0110 0111 0112 0120 0122 0200 0202 0220 0222 1000 1001 1002 1010 1011 1012 1020 1022 1100 1101 1102 1110 1111 1112 1120 1122 1200 1202 1220 1222 2000 2002 2020 2022 2200 2202 2220 2222

I have done this successfully, but my algorithm seems unnecessarily complicated and time-consuming (time is very important for my application). 我已成功完成此操作,但我的算法似乎不必要地复杂和耗时(时间对我的应用程序非常重要)。 Is there any way of either making it faster, or simplifying it if the speed cannot be improved? 有没有办法让它更快,或者如果速度无法提高就简化它?

Here is the program: 这是程序:

public static void count(int base, int size)
{
    int[] array = new int[size];
    print(array); // private print method prints out the array
    int index = 0;
    while (index < array.length)
    {
        if (array[index] < base - 1)
        {
            // check whether we need to increase array[index] by extra to maintain the order
            if (array[index] == 0)
            {
                int i;
                // search for the next nonzero digit
                // this search seems to take unnecessary time; is there a faster alternative?
                for (i = index + 1; i < array.length && array[i] == 0; i++);

                // check whether there was, in fact, some later nonzero digit
                if (i < array.length) array[index] = array[i];
                else                  array[index] = 1;
            }

            else array[index]++;

            print(array);
            index = 0;
        }

        // carry over to the next digit
        else array[index++] = 0;
    }
}

I would go for a recursive solution: 我会寻求一个递归的解决方案:

public static void count(int base, int size) {
    int[] configuration = new int[size];
    placeDigits(configuration, base, 0, 1);
}

public static void placeDigits(int[] configuration, int base, int pos, int minNonZero) {
    if (pos >= configuration.length) {
        print(configuration);
    } else {
        // 0 is a possible candidate
        configuration[pos] = 0;
        placeDigits(configuration, base, pos + 1, minNonZero);
        // digits between minNonZero and base
        for (int d = minNonZero; d < base; d++) {
            configuration[pos] = d;
            placeDigits(configuration, base, pos + 1, d);
        }
    }
}

It places digits one after the other into the array and observes the constraint that the non-zero digits must be non decreasing. 它将数字一个接一个地放入数组中,并观察到非零数字必须不减少的约束。

Okay, this is a bit of a cheat, but here's a solution expressed in pseudocode: 好吧,这有点作弊,但这是一个以伪代码表示的解决方案:

results : list
for i in 1..max
   if '0' not in str(i)
       append i to results
   fi
rof
print results

On the other hand, is it a cheat? 另一方面, 是骗子吗? "numbers with nonzero digits" is inherently a question about the decimal representation of the numbers, not in some sense the numbers themselves. “具有非零数字的数字”本质上是关于数字的十进制表示的问题,而不是某种意义上的数字本身。

Time complexity is O ( n ) of course -- at least counting str(i) as a single step, which is where it is a little bit of a cheat. 时间复杂度当然是On ) - 至少将str(i)为一个步骤,这是一个有点作弊的地方。

Just for fun, here's the same solution in Python: 只是为了好玩,这是Python中的相同解决方案:

print [i for i in xrange(max) if '0' not in str(i)]

And a sketch of a recursive solution: 以及递归解决方案的草图:

Let dig be a list of the nonzero digits, ie, ['1','2','3','4','5','6','7','8','9'] . dig成为非零数字的列表,即['1','2','3','4','5','6','7','8','9'] Enumerate all strings on that list of length ceil(log10(max)) (quiz question, why that limit?). 枚举该长度ceil(log10(max))列表中的所有字符串ceil(log10(max)) (测验问题,为什么要限制?)。

Print those strings in order, stopping when max is exceeded. 按顺序打印这些字符串,超过max时停止。

If you don't mind keeping the numbers in memory, you could code the following algorithm: 如果您不介意将数字保存在内存中,则可以编写以下算法:

  1. Start with the numbers 0,1...base-1 从数字0,1 ... base-1开始
  2. For each added digit, d , first add zero, then all previous numbers that begin with digits d or higher (indexing those by starting digit and number of digits, you could access them directly). 对于每个添加的数字, d ,首先添加零,然后添加以数字d或更高开头的所有先前数字(通过起始数字和数字编号索引,您可以直接访问它们)。

Or, as some like to phrase, dp style: Let dp[i][j] represent the sequence of numbers with i digits and left-most digit j . 或者,有些人喜欢短语, dp样式:让dp[i][j]表示带有i位数和最左边数字j数字序列。 Then dp[i][j] = [d] ++ map (d +) dp[l][k], for all l < i and k >= j, where d = j * 10 ^ (i - 1) 然后dp[i][j] = [d] ++ map (d +) dp[l][k], for all l < i and k >= j, where d = j * 10 ^ (i - 1)

(I borrowed the ++ from Haskell, where it often means to concat lists). (我从Haskell借用了++ ,它通常意味着连接列表)。

For example, base 4, 3 digits: 例如,基数4,数字3:

Start with one digit:
0,1,2,3

Add to the second digit from the first sequence:
10,11,12,13
20,22,23
30,33

Third digit, add from all previous sequences:
100,101,102,103
110,111,112,113
120,122,123
130,133

200,202,203
220,222,223
230,233

300,303
330,333

JavaScript code: JavaScript代码:

var base = 4;

var dp = [,[]];

for (var j=0; j<base; j++){
  dp[1][j] = [j];
}

for (var i=2; i<4; i++){
  dp[i] = [];
  for (var j=1; j<base; j++){
    var d = j * Math.pow(10,i - 1);
    dp[i][j] = [d];
    for (var l=1; l<i; l++){
      for (var k=j; k<base; k++){
        dp[i][j] = dp[i][j].concat(
                     dp[l][k].map(function(x){
                       return d + x;
                     }));
      }
    }
  }
}

console.log(JSON.stringify(dp))

/*
 [null,[[0],[1],[2],[3]]
,[null,[10,11,12,13]
,[20,22,23]
,[30,33]]
,[null,[100,101,102,103,110,111,112,113,120,122,123,130,133]
,[200,202,203,220,222,223,230,233]
,[300,303,330,333]]]
*/

Quite an interesting program you have written. 你写的很有趣的程序。

I've tried to increase the performance of the nested search, but so far I haven't found a way to make the worst-case scenario of searching for the next nonzero digit less than O(n). 我试图提高嵌套搜索的性能,但到目前为止,我还没有找到一种方法来查找搜索下一个小于O(n)的非零数字的最坏情况。

In the worst-case scenario, the subarray A[i..array.length-1] is not sorted, and array[i] = 0,therefore to find the next non-zero digit, you have to do a linear search. 在最坏的情况下,子阵列A [i..array.length-1]没有排序,并且array [i] = 0,因此要找到下一个非零数字,你必须进行线性搜索。

Aditionally, if there is no next non-zero digit, you have to search the whole array to "find it". 另外,如果没有下一个非零数字,则必须搜索整个数组以“找到它”。

(For example: we have that i = 1 for the sequence '0040'. The subarray [0, 4, 0] is not sorted, so you have to do a linear search to find the next largest/smallest nonzero digit, which would be located in array[2]) (例如:对于序列'0040'我们有i = 1.子阵列[0,4,0]没有排序,所以你必须进行线性搜索才能找到下一个最大/最小的非零数字,这将是位于数组[2])

The complexity for the worst case will be O(n). 最坏情况的复杂性将是O(n)。

Can you improve running time? 你能改善跑步时间吗? I guess you can if you do some parallel programming, but I have no knowledge of that field to help you, unfortunately. 我想如果你做一些并行编程你可以,但不幸的是我不知道那个领域可以帮助你。

I didn't measure performance, but think my code is better readable. 我没有测量性能,但认为我的代码更易读。 The idea is, to produce every number of base b and length l by Integer-iteration from 0 to the known number in decimal, using the Java-build-in conversion decimal to base b, then removing the zeros in that number (which is of type String) and testing for ascending order. 我们的想法是,通过整数迭代从0到十进制的已知数生成每个基数b和长度l,使用Java内置转换十进制到基数b,然后删除该数字中的零(这是类型为String)并测试升序。

The output has to be padded with zeros, so therefore the complicated printf in the end. 输出必须用零填充,因此最后是复杂的printf。

public static boolean isAscending (String digits) {
    for (int i = 1; i < digits.length (); ++i)
        if (digits.charAt (i-1) > digits.charAt (i)) 
            return false;
    return true;
}

public static void count (int base, int size)
{
    /** 
        Build numbers,i.e. from 000 to 333, for base 4 at length 3
        or 4^3 = 4*4*4 = 64 combinations 
    */      
    double max = Math.pow (base, size);
    for (int i = 0; i < max; ++i)
    {
        String res = Integer.toString (i, base);
        if (isAscending (res.replaceAll ("0", "")))
            System.out.printf ("%0"+size+"d ", Long.parseLong (res)); 
    }
}

This recursive function tries to avoid any unnecessary loop 这个递归函数试图避免任何不必要的循环

 public static void count0(int min, int ptr)
 {
      int me = 0; // joker
      do {
            array[ptr] = me;
            if (ptr > 0) count0(Math.max(me,min), ptr-1);
            else print(array);
            me = me == 0 ? (min > 0 ? min : 1) : me+1;
      } while (me < base);
 }

Called like this (base 8 for length of 17) to carry less arguments: 这样调用(长度为17的基数为8)来携带较少的参数:

 static int array[];
 static int base;

      int leng = 17;
      base = 8;
      array = new int [leng];

      count0 (0, array.length-1);

Recursivity has its price, though. 然而,递归有其代价。

Late to the party for this faster answer: 迟到了这个更快的答案:

Base               8
Size              20 digits
Current solution: 79 seconds (76~82)
Solution below:   23 seconds (22~24)
Possible numbers: 12245598208

without prints. 没有印刷品。 Principle: 原理:

The rule "a digit may be followed by a 0 or a digit >= preceding ones" is also valid for (valid) groups of digits: "a group may be followed by a group of zeroes, or a group which smaller digit is >= any of the preceding ones among the preceding groups". 规则“一个数字后跟一个0或一个数字> =前面的数字”对于(有效的)数字组也是有效的:“一个组后面可以跟一组零,或者一个小数字组是> =前面各组中的任何一个“。 Processing is done at the group level, rather than at the digit level. 处理在组级别完成,而不是在数字级别完成。

Given T total size, and N smaller number of digits in each group (T % N == 0), by calculating all possible groups of N digits they can then be assembled together (T / N groups per solution). 给定T总大小,并且每组中N个较小的位数(T%N == 0),通过计算所有可能的N个数字组,然后可以将它们组合在一起(每个解决方案的T / N组)。

  • pre-calculate all possible digits on a smaller size, eg 5 (2668 numbers), in an array (takes less than half a second) 在数组中预先计算较小尺寸的所有可能数字,例如5(2668个数字)(不到半秒)
  • keep the maximum digit for each of the "parts" in another array 保持另一个数组中每个“部分”的最大数字
  • set in another "atleast" array the indexes of groups based on their smaller digit 在另一个“atleast”数组中设置基于其较小数字的组的索引
  • build the large numbers by sticking all possible chunks (eg 4x5), provided that the lower digit of a group has to be >= highest of the preceding groups. 通过粘贴所有可能的块(例如4x5)来构建大数,只要组的低位必须> =前面组中的最高位。

Sample code to precalculate the small chunks (parts) 用于预先计算小块(部件)的示例代码

 static ArrayList<int[]> parts = new ArrayList<int[]>();
 static ArrayList<ArrayList<Integer>> atleast = new ArrayList<ArrayList<Integer>>();
 static ArrayList<Integer> maxi = new ArrayList<Integer>();
 static int stick[];
 static int base;
 static long num = 0;

 public static void makeParts(int min, int ptr)
 {
      int me = 0;
      do {
            array[ptr] = me;
            if (ptr > 0) makeParts(Math.max(me,min), ptr-1);
            else {
                 // add part
                 int[] newa = new int [array.length];
                 int i,mi,ma,last=array.length-1;
                 for (i=0 ; i<array.length ; i++) newa[i] = array[i];
                 parts.add(newa);
                 // maxi
                 for (i=0 ; i<=last && newa[i]==0 ; i++) /* */;
                 maxi.add(ma = i<=last ? newa[i] : 0);
                 // mini
                 for (i=last ; i>=0 && newa[i]==0 ; i--) /* */;
                 mi = i>=0 ? newa[i] : 0;
                 // add to atleast lists
                 int pi = parts.size() - 1;
                 ArrayList<Integer> l;
                 int imi = mi == 0 ? base-1 : mi;
                 for (i=0 ; i<=imi ; i++) {
                      if (i < atleast.size()) l = atleast.get(i);
                      else {
                            l = new ArrayList<Integer>();
                            atleast.add(i, l);
                      }
                      l.add(pi);
                 }
            }
            me = me == 0 ? (min > 0 ? min : 1) : me+1;
      } while (me < base);
 }

Sticking the "parts" 坚持“部分”

 public static void stickParts(int minv, int ptr)
 {
      // "atleast" gives indexes in "parts" of groups which min digit
      // is at least "minv" (or only zeroes)
      for (int pi: atleast.get(minv)) {
            stick[ptr] = pi;
            if (ptr > 0) {
                 stickParts(Math.max(minv,maxi.get(pi)), ptr-1);
            }
            else {
                 // count solutions
                 // the number is made of "parts" from indexes
                 // stored in "stick"
                 num++;
            }
      }
 }

Calling this in "main" 在“主要”中调用此

      base = 8;
      int leng  = 20;
      int pleng =  4;

      array = new int [pleng];

      makeParts(0,array.length-1);

      num = 0;
      stick = new int [leng / pleng];
      stickParts(0, (leng/pleng) - 1);

      out.print(String.format("Got %d numbers\n", num));

If T (total size) is prime, for instance, another specific group has to be calculated, eg for size 17, we could have 3 groups (of 5 digits) + one group of two digits. 例如,如果T(总大小)是素数,则必须计算另一个特定组,例如对于大小17,我们可以有3组(5位数)+一组两位数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM