2014-08-30 14 views
5

Data una sequenza infinita in questo modo (virgole inseriti per rendere modello più evidente):Come posso ottimizzare questa classe che risolve questa sequenza matematica

1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5, 1 2 3 4 5 6, 1 2 3 4 5 6 7, 1 2 3 4 5 6 7 8, 1 2 3 4 5 6 7 8 9, 1 2 3 4 5 6 7 8 9 1 0, 1 2 3 4 5 6 7 8 9 1 0 1 1, 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2, 1 2 3. . . . . . . . . .

mi viene data un indice (1 < = indice < = 10^10) e ho bisogno di trovare ciò cifra è in tale indice.

Ho scritto questo codice di lavoro ma è troppo lento. L'ho ottimizzato il più possibile ma non è ancora abbastanza. C'è un altro modo in cui posso farlo correre più velocemente?

public class Foo { 

    private static Scanner sc = new Scanner(System.in); 
    private static long input; 
    private static long inputCounter = 0; 
    private static int numberOfInputs; 


    public static void main(String[] args) { 
     numberOfInputs = Integer.parseInt(sc.nextLine().trim()); 

     while (inputCounter != numberOfInputs) { 
      input = Long.parseLong(sc.nextLine().trim()); 
      System.out.println(step()); 
      inputCounter++; 
     } 
    } 

    public static char step() { 
     int incrementor = 1; 
     long _counter = 1L; 

     while (true) { 
      for (int i = 1; i <= incrementor; i++) { 
       _counter += getNumberOfDigits(i); 

       if (_counter > input) { 
        return ((i + "").charAt((int)(input - _counter 
          + getNumberOfDigits(i)))); 
       } 
      } 
      incrementor++; 
     } 
    } 

    private static long getNumberOfDigits(int n) { 
     // 5 or less 
     if (n < 100) { 
      // 1 or 2 
      if (n < 10) 
       return 1; 
      else 
       return 2; 
     } else { 
      // 3 or 4 or 5 
      if (n < 1000) 
       return 3; 
      else { 
       // 4 or 5 
       if (n < 10000) 
        return 4; 
       else 
        return 5; 
      } 
     } 
    } 
} 

EDIT: credito al Marian's method of getting the number of digits in a number. suo divide et impera metodo che ho chiamato getNumberOfDigits (int n) accelerato la mia esecuzione del programma un sacco. Inizialmente ero convertendo il numero in una stringa quindi chiamando length() e che stava prendendo molto più tempo di quanto mi aspettassi

EDIT2: Alcuni campioni di I/O:

1 : 1 
2 : 1 
3 : 2 
4 : 1 
5 : 2 
6 : 3 
7 : 1 
8 : 2 
9 : 3 
10 : 4 
11 : 1 
12 : 2 
13 : 3 
14 : 4 
15 : 5 
16 : 1 
17 : 2 
18 : 3 
19 : 4 
20 : 5 
21 : 6 
22 : 1 
23 : 2 
24 : 3 
25 : 4 
26 : 5 
27 : 6 
28 : 7 
29 : 1 
30 : 2 
31 : 3 
32 : 4 
33 : 5 
34 : 6 
35 : 7 
36 : 8 
37 : 1 
38 : 2 
39 : 3 
40 : 4 
41 : 5 
42 : 6 
43 : 7 
44 : 8 
45 : 9 
46 : 1 
47 : 2 
48 : 3 
49 : 4 
50 : 5 
51 : 6 
52 : 7 
53 : 8 
54 : 9 
55 : 1 
56 : 0 
57 : 1 
58 : 2 
59 : 3 
60 : 4 
61 : 5 
62 : 6 
63 : 7 
64 : 8 
65 : 9 
66 : 1 
67 : 0 
68 : 1 
69 : 1 
70 : 1 
71 : 2 
72 : 3 
73 : 4 
74 : 5 
75 : 6 
76 : 7 
77 : 8 
78 : 9 
79 : 1 
80 : 0 
81 : 1 
82 : 1 
83 : 1 
84 : 2 
85 : 1 
86 : 2 
87 : 3 
88 : 4 
89 : 5 
90 : 6 
91 : 7 
92 : 8 
93 : 9 
94 : 1 
95 : 0 
96 : 1 
97 : 1 
98 : 1 
99 : 2 
+1

se mi danno l'input come 66 quello che sarebbe il risultato atteso? – OnePunchMan

+1

Sembra che la funzione 'passo' dovrebbe essere" letta fino al prossimo separatore "e non dovresti preoccuparti del numero di cifre ... Soprattutto dal momento che il tuo codice di cifre non sembra tenere conto di tutte le possibilità in uno spazio infinito. .. – abiessu

+0

@kaze Otterrete una risposta di 1 – Ogen

risposta

4

Il seguente codice è un calcolo quasi diretta. Produce esattamente gli stessi risultati di @maaartinus (vedi i risultati di seguito) ma lo fa in < 1 ms anziché 30 ms.

Vedere i commenti del codice per i dettagli su come funziona. Fammi sapere se ho bisogno di spiegarmi un po 'di più.

package com.test.www; 

    import java.util.ArrayList; 
    import java.util.List; 

    public final class Test { 

     /** <p> 
     * Finds digit at {@code digitAt} position. Runs in O(n) where n is the max 
     * digits of the 'full' number (see below), e.g. for {@code digitAt} = 10^10, 
     * n ~ 5, for 10^20, n ~ 10. 
     * <p> 
     * The algorithm is thus basically a direct 'closed form' calculation. 
     * It finds the quadratic equation to calculate triangular numbers (x(x+1)/2) but also 
     * takes into a account the transitions from 9 to 10, from 99 to 100, etc. and 
     * adjusts the quadratic equation accordingly. This finds the last 'full' number 
     * on each 'line' (see below). The rest follows from there. 
     *  
     */ 
     public static char findDigitAt(long digitAt) { 

      /* The line number where digitAt points to, where: 
      * 1, 1 2, 1 2 3, 1 2 3 4, etc. -> 
      * 1  <- line 1 
      * 1 2  <- line 2 
      * 1 2 3 <- line 3 
      * 1 2 3 4 <- line 4 
      */ 
      long line; 

      // ---- Get number of digits of 'full' numbers where digitAt at points, e.g. 
      //  if digitAt = 55 or 56 then digits = the number of digits in 10 which is 2. 

      long nines = 0L; // = 9 on first iteration, 99 on second, etc. 
      long digits = 0; 
      long cutoff = 0; // Cutoff of digitAt where number of digits change 
      while (digitAt > cutoff) { 
       digits++; 
       nines = nines + Math.round(Math.pow(10L, digits-1L)) * 9L; 

       long nines2 = 0L; 
       cutoff = 0L; 
       for (long i = 1L; i <= digits; i++) { 
        cutoff = cutoff + ((nines-nines2)*(nines-nines2+1)/2); 
        nines2 = nines2 + Math.round(Math.pow(10L, i-1L)) * 9L; 
       } 
      } 

      /* We build a quadratic equation to take us from digitAt to line */ 

      double r = 0; // Result of solved quadratic equation 
          // Must be double since we're using Sqrt() 
          // even though result is always an integer. 

      // ---- Define the coefficients of the quadratic equation 
      long xSquared = digits; 
      long x = 0L; 
      long c = 0L; 
      nines = 0L; // = 9 on first iteration, 99 on second, etc. 

      for (long i = 1L; i <= digits; i++) { 
       x = x + (-2L*nines + 1L); 
       c = c + (nines * (nines - 1L)); 
       nines = nines + Math.round(Math.pow(10L, i-1L)) * 9L; 
      } 
      // ---- Solve quadratic equation, i.e. y - ax^2 + bx + c => x = [ -b +/- sqrt(b^2 - 4ac) ]/2 
      r = (-x + Math.sqrt(x*x - 4L*xSquared*(c-2L*digitAt)))/(2L*xSquared); 

      // Make r an integer 
      line = ((long) r) + 1L; 
      if (r - Math.floor(r) == 0.0) { // Simply takes care of special case 
       line = line - 1L; 
      } 

      /* Now we have the line number ! */ 

      // ---- Calculate the last number on the line 
      long lastNum = 0; 
      nines = 0; 
      for (int i = 1; i <= digits; i++) { 
       long pline = line - nines; 
       lastNum = lastNum + (pline * (pline+1))/2; 
       nines = nines + Math.round(Math.pow(10, i-1)) * 9; 
      } 

      /* The hard work is done now. The piece of cryptic code below simply counts 
      * back from LastNum to digitAt to find first the 'full' number at that point 
      * and then finally counts back in the string representation of 'full' number 
      * to find the actual digit. 
      */ 
      long fullNumber = 0L; 
      long line_decs = 1 + (int) Math.log10(line); 
      boolean done = false; 
      long nb; 
      long a1 = Math.round(Math.pow(10, line_decs-1)); 
      long count_back = 0; 
      while (!done) { 
       nb = lastNum - (line - a1) * line_decs; 
       if (nb-(line_decs-1) <= digitAt) { 
        fullNumber = line - (lastNum - digitAt)/line_decs; 
        count_back = (lastNum - digitAt) % line_decs; 
        done = true; 
       } else { 
        lastNum = nb-(line_decs); 
        line = a1-1; 
        line_decs--; 
        a1 = a1/10; 
       } 
      } 

      String numStr = String.valueOf(fullNumber); 
      char digit = numStr.charAt(numStr.length() - (int) count_back - 1); 

      //System.out.println("digitAt = " + digitAt + " - fullNumber = " + fullNumber + " - digit = " + digit); 
      System.out.println("Found " + digit + " at position " + digitAt); 
      return digit; 
     } 

     public static void main(String... args) { 
      long t = System.currentTimeMillis(); 

      List<Long> testList = new ArrayList<Long>(); 
      testList.add(1L); testList.add(2L); testList.add(3L); testList.add(9L); 
      testList.add(2147483647L); 
      for (int i = 1; i <= 18; i++) { 
       testList.add(Math.round(Math.pow(10, i-1)) * 10); 
      } 
      //testList.add(4611686018427387903L); // OVERFLOW OCCURS 

      for (Long testValue : testList) { 
       char digit = findDigitAt(testValue); 
      } 

      long took = t = System.currentTimeMillis() - t; 
      System.out.println("Calculation of all above took: " + t + "ms"); 
     } 


    } 

Risultati

Found 1 at position 1 
    Found 1 at position 2 
    Found 2 at position 3 
    Found 3 at position 9 
    Found 2 at position 2147483647 
    Found 4 at position 10 
    Found 1 at position 100 
    Found 4 at position 1000 
    Found 9 at position 10000 
    Found 2 at position 100000 
    Found 6 at position 1000000 
    Found 2 at position 10000000 
    Found 6 at position 100000000 
    Found 8 at position 1000000000 
    Found 1 at position 10000000000 
    Found 1 at position 100000000000 
    Found 9 at position 1000000000000 
    Found 8 at position 10000000000000 
    Found 3 at position 100000000000000 
    Found 7 at position 1000000000000000 
    Found 6 at position 10000000000000000 
    Found 1 at position 100000000000000000 
    Found 1 at position 1000000000000000000 
    Calculation of all above took: 0ms 
5

Credo che i numeri triangolari entrano in gioca qui se guardiamo le posizioni delle cifre:

Position: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15, 16 17 18 19 20 21, 22 23 24 25 26 27 28 
Number: 1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5, 1 2 3 4 5 6, 1 2 3 4 5 6 7, 

Chiamare questa sequenza N (p).

ora un'occhiata ai numeri triangolari che hanno la formula k (k + 1)/2

k   : 1 2 3 4 5  6 
k(k+1)/2  : 1 3 6 10 15 21  triangle numbers 
k(k+1)/2+1 : 2 4 7 11 16 22  plus one 
N(k(k+1)/2+1): 1 1 1 1 1  1  item at this position 

modo da l'articolo subito dopo il numero triangolare n-esimo è sempre 1.

Dare una posizione p possiamo trovare lo k più vicino in modo che k (k + 1)/2 +1 < = p. Possiamo risolvere i x quadratica (x + 1)/2 + 1 = p riordinando

0.5 x^2 + 0.5 x + 1 - p = 0. 

Così a = 0,5, b = 0,5, c = 1-p. Risolvendo per x dà

x = -0.5 +/- sqrt(0.25 - 2 * (1-p)) 

prendere il segno positivo questo dà questi valori

1 0 
2 1 
3 1.5615528128 
4 2 
5 2.3722813233 
6 2.7015621187 
7 3 
8 3.2749172176 
9 3.5311288741 
10 3.7720018727 
11 4 
12 4.216990566 
13 4.4244289009 
14 4.623475383 
15 4.8150729064 
16 5 

Quindi, se prendiamo k = piano (-0,5 +/- sqrt (2 p - 1,75)) troviamo la numero k. Quindi trova l = p-k (k + 1)/2 che dà la cifra nel punto p-esimo.

Come sottolineato, questo non riesce non appena otteniamo due cifre. Ma potremmo fare un aggiustamento. Possiamo ottenere una formula "numero-cifra triangolare" TD (k). Che si comporta come numeri triangolari, T (k), per k < 10, ma aggiunge le cifre aggiuntive.

k  : 1 ... 9 10 11 12 
T(k) : 1  45 55 66 78 
change    1 3 6 
TD(k) : 2  45 56 69 84 

vediamo che per il 10 < = k < = 99 abbiamo solo bisogno di aggiungere T (k) + T (k-9). Questo dovrebbe darci un altro quadratico che potremmo risolvere.Simile accade per 100 < = k < = 999 con T (k) + T (k-9) + T (k-99).

Now T(k)+T(k-9) + 1 = k(k+1)/2 +(k-9)(k-8)/2 + 1 
        = 0.5 k^2 + 0.5 k + 0.5 k^2 - 17/2 k + 72/2 + 1 
        = k^2 -8 k + 37 

risolvere x^2 -8 k + 37 - p = 0 dà

x = (8 +/- sqrt(64 - 4 *(37-p))) /2 
    = (8 +/- sqrt(4 p - 64))/2 
    = 4 +/- sqrt(p - 21) 

prendendo il piano di questo ci dà il valore k.


vogliamo trovare la somma di triangoli T (k) + T (k-9) + T (k-99) + .... In prima approssimazione T (kn) = T (n) per ogni n. Quindi la somma è semplicemente d * T(k) dove d nel numero di cifre di k. T (k) è circa k^2/2 quindi la somma è approssimativamente d * k^2/2. Questo è facile da risolvere, d essere il numero di cifre della posizione p, quindi k = sqrt(2*p/d). Potresti usare questo per ottenere un'ipotesi approssimativa per k.

+0

Errore per p = 67. – rslemos

+0

Fallisce per p = 55, p = 56, p = 57, (forse tutti tra parentesi), p = 67 – rslemos

+0

L'idea di usare numeri triangolari è davvero intelligente, ma le cose iniziano a diventare disordinate quando la sequenza interna raggiunge 10, che occupa 2 posizioni (al massimo), e poi quando raggiunge 100, che occupa 3 posizioni (al massimo di nuovo) e così via. Qualsiasi formula chiusa dovrebbe in qualche modo considerare la base in cui sono rappresentati i numeri (nel caso OP: la base 10). – rslemos

2

ho scritto un program senza molto pensare ...

  • length(n) calcola il numero di cifre della rappresentazione decimale di n
  • cummulativLength(n) calcola il numero totale di cifre per una sequenza che termina con n
  • doublyCummulativLength(n) calcola il numero totale di cifre per tutte le sequenze che terminano al massimo n
  • fullSequenceBefore(pos) calcola la più lunga sequenza completa prima posizione pos mediante ricerca binaria
  • digitAt(n) calcola la cifra nella posizione n calcolando prima fullSequenceBefore e sottraendo la sua lunghezza; poi usa un altro ricerca binaria per l'ultima sequenza

ho usato long ovunque come è dannatamente veloce. C'è un rudimentale test e demo produrre i seguenti risultati

Found 1 at position 1 
Found 1 at position 2 
Found 2 at position 3 
Found 3 at position 9 
Found 2 at position 2147483647 
Found 4 at position 10 
Found 1 at position 100 
Found 4 at position 1000 
Found 9 at position 10000 
Found 2 at position 100000 
Found 6 at position 1000000 
Found 2 at position 10000000 
Found 6 at position 100000000 
Found 8 at position 1000000000 
Found 1 at position 10000000000 
Found 1 at position 100000000000 
Found 9 at position 1000000000000 
Found 8 at position 10000000000000 
Found 3 at position 100000000000000 
Found 7 at position 1000000000000000 
Found 6 at position 10000000000000000 
Found 1 at position 100000000000000000 
Found 1 at position 1000000000000000000 
Found 7 at position 4611686018427387903 

Computed in 0.030 seconds. 

Il maggior numero ho provato per è Long.MAX_VALUE/2. In teoria potrebbe funzionare anche per lo Long.MAX_VALUE, ma qui sto andando in overflow.

+0

+1 per scrivere codice leggibile (e corretto!). – Floris

+0

@Floris [Non tutti] (http://codereview.stackexchange.com/a/61597/14363) sarebbe d'accordo (notare che ho migliorato il codice dopo la revisione). – maaartinus

+0

Non riesco a trovare un errore 'off by one' per qualsiasi valore di input utilizzando il codice. Puoi darmi un esempio (Ovviamente non posso testare tutti i valori di input :) – Floris

3

Ho aggiunto del codice che migliora notevolmente il tempo di esecuzione - salta in fondo per vedere gli esempi.

Un'intuizione chiave che ho trovato è che puoi saltare sottosequenze se l'input non giace da nessuna parte in esso. Ad esempio, se stai cercando il numero 1.000.000.000, sai che non è nella quinta sottosequenza {1,2,3,4,5}. Quindi, perché iterare su di esso? Questa versione sembra essere molto più veloce (prova a eseguirlo con un input di 1000000000 e vedi la differenza di orario), e per quanto posso dire restituisce lo stesso risultato in tutti i casi.

Così, il mio algoritmo tiene traccia della lunghezza della sottosequenza (Aggiungere il numero di cifre su ogni iterazione), e la sottosuccessione siamo in. Se l'input è maggiore della lunghezza della sottosequenza, basta sottrarre quella lunghezza e ripetere di nuovo. Se è più piccolo (o uguale, poiché il problema è 1-indicizzato), inizia a scomporre quella sotto-sequenza.

Una nota minore: ho anche aggiornato getNumberOfDigits in modo che possa gestire qualsiasi numero eseguendolo in modo ricorsivo, ma sia le nuove che le vecchie versioni si basano su questo nuovo metodo in modo che non ottenga credito per il miglioramento del tempo.

public class Foo { 

private static Scanner sc = new Scanner(System.in); 
private static long input; 
private static long inputCounter = 0; 
private static int numberOfInputs; 


/** Updated main method that calls both the new and old step() methods 
* to compare their outputs and their respective calculation times. 
* @param args 
*/ 
public static void main(String[] args) { 
    numberOfInputs = Integer.parseInt(sc.nextLine().trim()); 

    while (inputCounter != numberOfInputs) { 
     long i = Long.parseLong(sc.nextLine().trim()); 
     input = i; 
     System.out.println("Processing " + input); 
     long t = System.currentTimeMillis(); 
     System.out.println("New Step result - " + newStep() + " in " + (System.currentTimeMillis() - t)+"ms"); 
     input = i; 
     t = System.currentTimeMillis(); 
     System.out.println("Old Step result - " + step() + " in " + (System.currentTimeMillis() - t)+"ms"); 
     inputCounter++; 
    } 
} 

/** Old version of step() method given in question. Used for time comparison */ 
public static char step() { 
    int incrementor = 1; 
    long _counter = 1L; 

    while (true) { 
     for (int i = 1; i <= incrementor; i++) { 
      _counter += getNumberOfDigits(i); 

      if (_counter > input) { 
       return ((i + "").charAt((int)(input - _counter 
         + getNumberOfDigits(i)))); 
      } 
     } 
     incrementor++; 
    } 
} 

/** New version of step() method. 
* Instead of iterating one index at a time, determines if the result lies within this 
* sub-sequence. If not, skips ahead the length of the subsequence. 
* If it does, iterate through this subsequence and return the correct digit 
*/ 
public static int newStep() { 
    long subSequenceLength = 0L; 
    long subSequenceIndex = 1L; 
    while(true){ 
     //Update to the next subsequence length 
     subSequenceLength += getNumberOfDigits(subSequenceIndex); 
     if(input <= subSequenceLength){ 
      //Input lies within this subsequence 
      long element = 0L; 
      do{ 
       element++; 
       long numbDigits = getNumberOfDigits(element); 
       if(input > numbDigits) 
        input -= numbDigits; 
       else 
        break; 
      }while(true); 

      //Correct answer is one of the digits in element, 1-indexed. 
      //Speed isn't that important on this step because it's only done on return 
      return Integer.parseInt(("" + element).substring((int)input-1, (int)input)); 


     } else{ 
      //Input does not lie within this subsequence - move to next sequence 
      input -= subSequenceLength; 
      subSequenceIndex++; 
     } 

    } 
} 

/** Updated to handle any number - hopefully won't slow down too much. 
* Won't handle negative numbers correctly, but that's out of the scope of the problem */ 
private static long getNumberOfDigits(long n){ 
    return getNumberOfDigits(n, 1); 
} 

/** Helper to allow for tail recursion. 
* @param n - the number of check the number of digits for 
* @param i - the number of digits thus far. Accumulator. */ 
private static long getNumberOfDigits(long n, int i) { 
    if(n < 10) return i; 
    return getNumberOfDigits(n/10, i+1); 
} 
} 

uscita Esempio miglioramento mostrando tempo:

> 8 
> 10000 
Processing 10000 
New Step result - 9 in 0ms 
Old Step result - 9 in 2ms 
> 100000 
Processing 100000 
New Step result - 2 in 0ms 
Old Step result - 2 in 4ms 
> 1000000 
Processing 1000000 
New Step result - 6 in 0ms 
Old Step result - 6 in 3ms 
> 10000000 
Processing 10000000 
New Step result - 2 in 1ms 
Old Step result - 2 in 22ms 
> 100000000 
Processing 100000000 
New Step result - 6 in 1ms 
Old Step result - 6 in 178ms 
> 1000000000 
Processing 1000000000 
New Step result - 8 in 4ms 
Old Step result - 8 in 1765ms 
> 10000000000 
Processing 10000000000 
New Step result - 1 in 11ms 
Old Step result - 1 in 18109ms 
> 100000000000 
Processing 100000000000 
New Step result - 1 in 5ms 
Old Step result - 1 in 180704ms 
+0

Buono ma lento. Ho bisogno di 27 ms per questo. – maaartinus

+0

Sono confuso ... Il risultato più lento che ho postato era 11ms. I tempi molto più lunghi sotto ogni risultato sono i tempi di esecuzione del codice originale. – Mshnik

+0

Mi scuso ... Vedo che non riesco a leggere. Ho richiesto 27 ms per il mio intero calcolo, quindi probabilmente siamo alla pari. – maaartinus

Problemi correlati