Otimizar código com números primos

Tenho o seguinte problema: preciso de um programa que receba de entrada um número par positivo e forme uma “rodinha” com esses números, de forma que:

-Todos os números de 1 a n sejam usados no círculo somente uma vez.
-A soma de dois números consecutivos seja um número primo.
-A soma de um número com o que está diametralmente oposto a ele também seja um número primo.

Por exemplo, uma rodinha de 18 números seria exibida da seguinte maneira em forma de vetor:
1 6 7 16 15 8 5 12 11 18 13 10 3 4 9 14 17 2.

Eu tenho o seguinte código, porém se vocês rodarem ele, verão que podem deixar rodando e fazer uma viagem para visitar os parentes que moram longe, e talvez, quando você voltar, terá resultado em alguma coisa. Preciso otimizar ele e, se possível, rodar em menos de um minuto:

‘’’

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.JOptionPane;

 /**
 * Desafio 5
 *
 * @author Conan,O Barbaro
 *
 */

public class QuintoDesafio {

private static int j;
private static int k;

static final long longLimit = (long) 500;
private static Vector primos  = new Vector();

public static void main(String[] args)
{

    long inicio = System.currentTimeMillis();

    ArrayList lista = new ArrayList();

    ArrayList<Long> primos = new ArrayList<Long>();

    long longNumber=5;
    int intNext=0, intIndex=0;
    double doubleSquareRoot=0.0;

    primos.add(new Long(2));
    primos.add(new Long(3));

    do {

        intNext = 0;
        doubleSquareRoot = Math.sqrt(longNumber);

        while ((double) primos.get(++intNext)<doubleSquareRoot && (longNumber%primos.get(intNext))!=0);

        if ((double) primos.get(intNext)>doubleSquareRoot) primos.add(new Long(longNumber));

        longNumber+=((longNumber%3==2)?2:4);

    } while (longNumber<longLimit);

    String n = JOptionPane.showInputDialog( "Informe n (par) ");
    int ene = Integer.parseInt( n );

    for ( int i=1; i <= ene; i++ ) {
        lista.add( new Integer(i) );
    }
    permuta(lista, 0);

    long fim  = System.currentTimeMillis();
    System.out.println(new SimpleDateFormat("ss.SSS").format(new Date(fim - inicio)));
}

static void permuta(ArrayList arr, int k){
    boolean passou = true;

    for(int i = k; i < arr.size(); i++){
        java.util.Collections.swap(arr, i, k);
        permuta(arr, k+1);
        java.util.Collections.swap(arr, k, i);
    }
    if (k == arr.size() -1){
        int metade = arr.size()/2;

        for ( int j=0; j<arr.size(); j++ ) {
            int um = (int) arr.get(j);
            int dois;
            if ( j+1 == arr.size() ) {
                dois = (int) arr.get(0);
            } else {
                dois = (int) arr.get(j+1);
            }
            Integer numteste = new Integer( um+dois );
            if ( !primos.contains( numteste )) {
                passou = false;
            }
        }

        for ( int j=0; j<metade; j++ ) {
            int um = (int) arr.get(j);
            int dois = (int) arr.get(j+metade);
            Integer numteste = new Integer( um+dois );
            if ( !primos.contains( numteste )) {
                passou = false;
            }
        }
        if ( passou ) {
            System.out.println(java.util.Arrays.toString(arr.toArray()));
        }
    }
}
}

‘’’

Eu fiz outro código seguindo uma lógica de embaralhar a lista até que se chegasse na solução, mas ela também demora demais para rodar e tem vezes que não chega no resultado. Se alguém puder me dar uma luz sobre este problema, fico agradecido.

  • Todos os números de 1 a n sejam usados no círculo somente uma vez.
  • A soma de dois números consecutivos seja um número primo.
  • A soma de um número com o que está diametralmente oposto a ele também seja um número primo.

no caso vc entra com N (par), certo?

é um problema interessante.

como eu faria:

  • faça um loop de 1 até 2*N - 1 e veja quais desses são primos. 1 não é considerado primo, 2 é, então vc pode ja eliminar essas duas condições.
  • vc pode usar diveras estruturas de dados para armazenar essa informação, por exemplo um array para armazenar essas informações. sabendo que arrays começam em 0 vc pode fazer
boolean isPrime[] = new boolean[2*N];
isPrime[1] = false;
isPrime[2] = true;
isPrime[3] = true;
...

como 0 não entra, podemos ignorar essa posição

como a soma capaz de trazer o maior numero é N + N-1, que é 2*N - 1, por tanto nesse momento vc tem todas as peças que vc precisa.

agora vamos dizer que vc tem a sua rodinha. pode ser um vetor de N posicoes.

Se N for 18, um numero na posição 0 tem como posição diametralmente oposta a posição 9.
o numero 1 tem como posição diametralmente oposta a posicao 10
o numero 18 tem como posical d. o. a posicao 9

para calcular vc precisaria fazer: posicao ( i + (N / 2)) % N
demonstrar isso fica a servico do leitor. pega um N igual a 4 e faz as contas :slight_smile:

pra saber se a soma de 2 numeros consecutivos eh primo, bastaria fazer

posicoes: i, (i +1) % N

como resolver

a) brute force:

vc faz todas as permutacoes possiveis de1 a N sem repetir.’ vc faz um laço em todas as posições vendo se as somas fazem sentido ( se os resultados sao primos)

vc vai executar 2 verificações ( a soma do consecutivo e a soma do diametralmente oposto ), N vezes

para N! combinações.

esse algorimo é O(N!) no pior caso.

para que fique mais eficiente, vc teria que restringir o espaço de busca.

b) busca inteligente.

primeiro, se vc tem N= 18, então

1 6 7 16 15 8 5 12 11 18 13 10 3 4 9 14 17 2 é uma resposta, porem

2 1 6 7 16 15 8 5 12 11 18 13 10 3 4 9 14 17 também é. existe uma simetria circular na resposta.

Segundo, pense no numero 1: vc tem poucas combinações de 1 + qq numero que resulte em um numero primo.

se vc pre-calcular as combinações de numeros que resultam em primos, vc restringe o seu espaço de busca.

vc não quer as permutaçoes inuteis.

e vc pode começar colocando um numero, por exemplo o numero 1, na posição 0 e ai testar as conbinações onde na posição 1 e a posição 9 vc tem numeros que, somados, dariam primos. e por ai vai.

Edit: isso parece ser um caso de buscar um Hamiltonian Path. existem alguns algoritmos para isso e vc precisa pensar em termos de grafos.

eu descobri a partir destes links:

boa sorte

Pensei em tudo isso, inclusive já tinha começado a escrever um código assim. Esse código que postei foi o que recebi do meu professor, extremamente ineficiente.

Como você disse, armazeno o 1 na primeira posição, aí vou pegar o 2 por exemplo. Aí verei que o 1 + 2 é primo, então armazeno o 2 na segunda posição, ou como vc disse, na nona posição. Mas e se depois eu precisar do número 2 para outra soma? Como eu vou fazer se eu precisar dos números que já estão armazenados? Entende, eu preciso misturar esse vetor novamente de modo que fique no formato desse círculo, e toda vez preciso verificar os anteriores pra ver se não preciso deles. Mas como meu programa vai adivinhar? Hahaha é bem complicado fazer sem recursão. Como em desafios anteriores que esse professor me passou, deve ter alguma solução matemática pra isso, ou sei lá.

:exploding_head:

se vc fizer as permutações, vc não vai repetir.

permutações vc pode resolver de forma recursiva

É, a solução do meu professor usa um método recursivo de permutações. Mas além disso a função é chamada dentro de um for. Hahahah