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

时间:2015-11-22 02:56:37

标签: java algorithm performance

我试图编写一个程序,该程序将多个数字和一个基数作为参数,并通过按升序排列非零数字的数字向上计数。例如,在带有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

并且在4位数的基数3中应该打印:

  <00> 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

我已成功完成此操作,但我的算法似乎不必要地复杂且耗时(对我的应用来说,时间非常重要)。有没有办法让它更快,或者如果无法提高速度就简化它?

以下是该计划:

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;
    }
}

7 个答案:

答案 0 :(得分:4)

我会选择递归解决方案:

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);
        }
    }
}

它将数字一个接一个地放入数组中,并观察到非零数字必须不减少的约束。

答案 1 :(得分:2)

好的,这有点像作弊,但这是用伪代码表示的解决方案:

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

另一方面,作弊? “具有非零数字的数字”本质上是关于数字的十进制表示的问题,而不是某种意义上的数字本身。

时间复杂度 O n )当然 - 至少将str(i)计为一步,这是它的一点点作弊。

只是为了好玩,这是Python中的相同解决方案:

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

一个递归解决方案的草图:

dig成为非零数字的列表,即['1','2','3','4','5','6','7','8','9']。枚举长度ceil(log10(max))列表中的所有字符串(测验问题,为什么要限制?)。

按顺序打印这些字符串,在超出max时停止。

答案 2 :(得分:1)

如果您不介意将数字保存在内存中,则可以编写以下算法:

  1. 从数字0,1 ... base-1
  2. 开始
  3. 对于每个添加的数字d,首先添加零,然后添加以数字d或更高数字开头的所有先前数字(通过起始数字和数字编号索引,您可以直接访问它们)
  4. 或者,有些人喜欢用短语dp样式:让dp[i][j]代表数字序列,其中i位数和最左边的数字j。然后dp[i][j] = [d] ++ map (d +) dp[l][k], for all l < i and k >= j, where d = j * 10 ^ (i - 1)

    (我从Haskell借用了++,这通常意味着连接列表。)

    例如,基数为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代码:

    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]]]
    */
    

答案 3 :(得分:0)

你写的很有趣的节目。

我试图提高嵌套搜索的性能,但到目前为止,我还没有找到一种方法来查找搜索下一个小于O(n)的非零数字的最坏情况

在最坏的情况下,子阵列A [i..array.length-1]没有排序,并且数组[i] = 0,因此要找到下一个非零数字,你必须做一个线性搜索。

另外,如果没有下一个非零数字,你必须搜索整个数组,然后找到它&#34;。

(例如:我们假设i = 1表示序列&#39; 0040&#39;。子阵列[0,4,0]没有排序,所以你必须进行线性搜索才能找到下一个最大/最小非零数字,它将位于数组[2]中

最坏情况的复杂性将是O(n)。

你能改善跑步时间吗?我想你可以做一些并行编程,但不幸的是,我不知道那个领域可以帮助你。

答案 4 :(得分:0)

我没有衡量效果,但认为我的代码更具可读性。 我们的想法是,通过整数迭代从0到十进制的已知数生成每个基数b和长度l,使用Java内置转换十进制到基数b,然后删除该数字中的零(这是类型为String)并测试升序。

输出必须用零填充,因此最后是复杂的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)); 
    }
}

答案 5 :(得分:0)

这个递归函数试图避免任何不必要的循环

 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);
 }

这样调用(长度为17的基数为8)来携带较少的参数:

 static int array[];
 static int base;

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

      count0 (0, array.length-1);

但递归有其代价。

答案 6 :(得分:0)

这个更快答案的晚会:

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

没有印刷品。原则:

规则“一个数字后跟一个0或一个数字&gt; =前面的数字”对于(有效的)数字组也是有效的:“一个组后面可以跟一组零,或者一个组较小的数字> =前面的组中的任何前面的数字“。处理在组级别完成,而不是在数字级别完成。

给定T总大小,并且每组中的N个较小的位数(T%N == 0),通过计算所有可能的N个数字组,然后可以将它们组合在一起(每个解决方案的T / N组)。

  • 预先计算较小尺寸的所有可能数字,例如5(2668个数字),在一个数组中(不到半秒)
  • 保留另一个数组中每个“部分”的最大数字
  • 在另一个“atleast”数组中设置基于其较小数字的组的索引
  • 通过粘贴所有可能的块(例如4x5)来构建大数字,前提是组的低位必须> = =前面组中的最高位。

预先计算小块(部分)的示例代码

 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);
 }

坚持“部分”

 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++;
            }
      }
 }

在“主要”中调用此

      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));

例如,如果T(总大小)是素数,则必须计算另一个特定组,例如,对于大小17,我们可以有3组(5位数)+一组两位数。