Algoritmos, Discussão

15 respostas
Vinicius_Zibetti_Res

Opa tudo bom ?

Tenho o seguinte algoritmo:

The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be:

1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

Let us list the factors of the first seven triangle numbers:

1: 1
3: 1,3
6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28

We can see that 28 is the first triangle number to have over five divisors.

What is the value of the first triangle number to have over five hundred divisors?

Referência: http://projecteuler.net/index.php?section=problems&id=12

Minha solução:

int n = 100000000;
		int cont = 0;
		for (int i = 1, j = 0; i <= n; i++, j++) {
			i = i + j;
			for (int k = 1; k < i; k++) {
				if (i % k == 0) {
					cont++;
				}

				if (cont == 500) {
					break;
				}
			}
			if (cont == 500) {
				System.out.println(i);
				break;
			}
			cont = 0;
		}

Só que esta solução esta muito, muito lerda mesmo..... Gostaria que alguem me ajudasse a aprimorar este codigo ou criar outro mais eficiente...

15 Respostas

fabiocsilva

Você poderia utilizar a fórmula do número triangular ao invés de dois laços for. Não sei se é mais rápido, mas vale a pena tentar.

A

Acho que o que mais consome tempo no seu código é o for para contar o número de divisores…

Está fazendo via força bruta, ou seja, testa todos os números de 0 a n para contar os divisores de n

Mas há uma maneira de você otimizar isto.
Com exceção de 1, todos os números terão uma quantidade de divisores par.

Analisando um dos casos:

Divisores de 28: 1,2,4,7,14,28
Repare que 28 = 28 * 1 = 14 * 2 = 7 * 4

Com isso em mente, você pode fazer uma otimização baseado no valor do divisor.
Se for maior que o “complemento” dele até o número, você multiplica por 2 e pára de contar…

No caso do 28, quando achar o divisor 7 ( o complemento seria 4), já pode parar de contar (sem incluir o 7), e multiplique por 2…

Ah…detalhe, se houver um divisor igual ao complemento (para n=16, teriamos 4 * 4, por exemplo), você teria que subtrair 1 após a multiplicação.

Fim um esboço aqui e ele calcula em menos de 5 segundos.

gomesrod

Aqui é preciso um pouco de cuidado, isso não é uma regra:
Divisores de 9: 1,3,9
de 25: 1,5,25

Uma maneira mais geral de colocar em prática sua idéia seria:

  • Procurar todos os divisores até a raiz quadrada do número (se não tiver raiz exata, ir até o número imediatamente anterior)
  • Multiplicar a quantidade por 2
  • Se o numero tem raiz exata, subtrai 1 (que é a raiz duplicada).
A

Já cito este caso no meu post.

gomesrod

Sim, reparei que você faz esse adendo. Mas aí seria mais correto dizer que todos os números tem quantidade par de divisores, com exceção daqueles que tem quantidade impar. :slight_smile:

Na verdade meu intuito era mostrar uma maneira mais padronizada de fazer essa verificação, fica mais enxuto pois não é preciso se preocupar com os “pares” de divisores.

A idéia central porém é a mesma.

pmlm

Obviamente que há uma “formula mágica” para responder a esta questão. Não tem nada a ver com procurar os divisores e contar. Inicialmente pensei que tivesse a ver com a divisão do número em factores primos. Procurei sobre o assunto e sim, tem a ver. O número de divisores de um número está directamente ligado com a sua divisão em factores primos.

Imaginemos o numero 100, que dividido em factores primos dá 2x2x5x5 ou seja 2^25^2. Pegando nos expoentes obtidos, somando um a cada e multiplicando temos: (2+1)(2+1)=9. Assim, o número 100 tem 9 divisores. Conferindo: 1, 2, 4, 5, 10, 20, 25, 50, 100.

Sabendo isto, podes implementar um algoritmo bem rápido. O meu demora menos de 0.5 segundos…

E

O que o PMLM disse é relativamente fácil de implementar.
A primeira coisa a se lembrar é que um número triangular é sempre da forma N (N+1) / 2.
A outra coisa a se lembrar é que se um número, fatorado, é da forma a^n + b^m + c^p (por exemplo), ele terá (n+1)(m+1)(p+1) divisores.
Portanto, você só precisa ter uma boa implementação do Crivo de Eratóstenes para poder fatorar um número.

cesar.oliveira

Respondendo ao seu post eu desenvolvi uma classe que faz esse calculo demora coisa de 1 segundo mais ou menos é bem rápido.

Para conseguir desenvolve tive que pesquisar algumas coisas.

Então antes de ler o código de uma olhada nesses links:

Saber todos os divisores de um número:

Implementei isso em um método chamado public HashSet<Integer> retornaListaDeDivesores(Integer numero){...}

Esse método retorna uma lista de todos os divisores de um determinado número.

Foi usado HashSet, pois o HashSet não deixa adicionar números iguais na lista.

Eis o código:

[size=18]Classe Principal[/size]

public class Principal {
	/**
	 * @autor Cesar Aparecido de Oliveira
	 */
	public static void main(String[] args) {
				Calculo c = new Calculo();
				System.out.println(c.retornaNumeroDeTriangulo(500));
	}
}

[size=18]Classe Calculo[/size]

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

public class Calculo {

	// Lista de numeros primos
	private List<Integer> listaPrimnos;

	public Calculo() {

		// passa por parâmetro quantos números primos quer ter
		listaPrimnos = carregaNumerosPrimos(30); // vai gerar os 30 primeiros
													// números primos
		retornaNumeroDeTriangulo(50);
	}

	public Integer retornaNumeroDeTriangulo(Integer quantidadeDivisores) {

		HashSet<Integer> divisores = new HashSet<Integer>();
		List<Integer> numerosTriangulos = new ArrayList<Integer>();

		int valor = 1;
		int contador = 2;
		numerosTriangulos.add(valor);

		while (divisores.size() < quantidadeDivisores) {

			valor = numerosTriangulos.get(contador - 2) + contador++;

			divisores = retornaListaDeDivesores(valor);

			numerosTriangulos.add(valor);
		}

		return valor;
	}

	// É utilizado os números primos para decompor o número que se quer saber a
	// quantidade de divisores
	public List<Integer> carregaNumerosPrimos(Integer quantidade) {
		List<Integer> lista = new ArrayList<Integer>();

		Integer contagem = 4;

		lista.add(2); // carrega os 4 primeiros números primos
		lista.add(3);
		lista.add(5);
		lista.add(7);

		// caso queira mais do que 4 números primos, ai vai gerando os próximos
		// de acordo com a quantidade que foi passada
		if (contagem < quantidade) {
			for (int i = 8; contagem < quantidade; i++) {[b]
				if (verificaPrimo(i, lista)) {
					lista.add(i);[/b]
					contagem++;
				}
			}
		}
		return lista;
	}

	private boolean verificaPrimo(int numero, List<Integer> lista) {
		boolean retorno = true;
		int posicao = 0;

		while (retorno && posicao < lista.size() - 1) {
			if (numero % lista.get(posicao++) == 0) {
				retorno = false;
			}
		}

		return retorno;
	}

	// vai gerar a lista de todos os divisores que o número contém
	public HashSet<Integer> retornaListaDeDivesores(Integer numero) {
		HashSet<Integer> lista = new HashSet<Integer>(); // é usado o Hashset
															// pois através dele
															// não é permitido
															// inserir valores
															// repetidos
		List<Integer> listaNumerosDecompostos = new ArrayList<Integer>();

		int posicao = 0;

		while (posicao < listaPrimnos.size() - 1 && numero != 1) {
			if (numero % listaPrimnos.get(posicao) == 0) {
				numero = numero / listaPrimnos.get(posicao);
				listaNumerosDecompostos.add(listaPrimnos.get(posicao));
			} else {
				posicao++;
			}
		}

		List<Integer> listaAuxiliar = new ArrayList<Integer>();

		listaAuxiliar.add(1); // todo numero é divisivel por 1
		lista.add(1);
		int i = 0;
		for (i = 0; i < listaNumerosDecompostos.size(); i++) {
			for (Integer v : listaAuxiliar) {
				lista.add(v * listaNumerosDecompostos.get(i));
			}
			listaAuxiliar.clear();
			listaAuxiliar.addAll(lista);
		}

		return lista;
	}
}

É isso ai

o primeiro número triângulo que possui 500 divisores é: 76576500

Bom o código pode ser melhorado, se tiver alguma coisa para melhorar só postar ae, qualquer dúvida estamos as ordens.

Abraços galera.

Acessem http://programejava.blogspot.com/

pmlm

A minha versão:

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;

class Triangulo{
  public static void main(String args[]){
    long start = System.currentTimeMillis();
    
     
    //identifica os número primos menores que 50 000
    boolean primes[] = new boolean[50000];
    for (int i = 2; i < primes.length; i++){
      boolean isPrime = true;
      for (int j = 2; j <= Math.sqrt(i); j++){
        if (i % j == 0){
          isPrime = false;
          break;
        }
      }
      primes[i] = isPrime;  
      
    }
   
  int current = 0;
  //percorre todos os numeros triângulo
  for (int i = 1; i < Integer.MAX_VALUE;i++){
      Map <Integer, Integer> map = new HashMap<Integer, Integer>();
      int j = 1;
      current+=i;
      int value = current;
      
      //decompoe em factores primos
      while(value != 1){
        j++;
        if (primes[j]) {
          if (value%j == 0){
            if (map.get(j) == null){
              map.put(j, 1);
            } else {
              map.put(j, map.get(j) + 1);
            } 
            value = value / j;           
            j = 1;        
          }
        }
      }
      
      //Verifica se tem 500 ou mais divisores
      Collection<Integer> values = map.values();
      int total = 1;
      for (Integer v: values){
        total *= (v + 1);
      }
      
      if (total > 499){                                
        long totalTime = System.currentTimeMillis() - start;
        System.out.println("O "+ i+"º número é " + current + " e tem " + total + " divisores");
        System.out.println(totalTime +" ms");
        break;
      
      }
    }   
  }
}


O 12375º número é 76576500 e tem 576 divisores
160 ms

pmlm

Foi feito quase em cima do joelho, tem detalhes que podem ser melhorados… Isso está simplesmente conforme fui pensando :slight_smile:

Vinicius_Zibetti_Res

Aqui é preciso um pouco de cuidado, isso não é uma regra:
Divisores de 9: 1,3,9
de 25: 1,5,25

Uma maneira mais geral de colocar em prática sua idéia seria:

  • Procurar todos os divisores até a raiz quadrada do número (se não tiver raiz exata, ir até o número imediatamente anterior)
  • Multiplicar a quantidade por 2
  • Se o numero tem raiz exata, subtrai 1 (que é a raiz duplicada).

Eu nao entendi muito bem esta parte aqui, fui tentar implementar e nao consegui, sera q por favor vc pode implemntar isso no meu coidog ali por favor?

gomesrod

Vinicius Zibetti Resko:
Aqui é preciso um pouco de cuidado, isso não é uma regra:
Divisores de 9: 1,3,9
de 25: 1,5,25

Uma maneira mais geral de colocar em prática sua idéia seria:

  • Procurar todos os divisores até a raiz quadrada do número (se não tiver raiz exata, ir até o número imediatamente anterior)
  • Multiplicar a quantidade por 2
  • Se o numero tem raiz exata, subtrai 1 (que é a raiz duplicada).

Eu nao entendi muito bem esta parte aqui, fui tentar implementar e nao consegui, sera q por favor vc pode implemntar isso no meu coidog ali por favor?


Depois do meu post o pessoal sugeriu uma maneira mais eficiente de contar os divisores, talvez vc queira dar uma olhada melhor nessa outra solução.

Vinicius_Zibetti_Res

ah eu andei olhando na internet e consegui aulcaçar um solução que calcula-o em 2 segundos.

public static void main(String[] args) {
		Calendar c1 = Calendar.getInstance();
		long inicio = c1.getTimeInMillis();
		int i = 1, j = 0;
		int numFatores = 0;
		int v = 0;
		while (numFatores < 501) {
			v = i * (i + 1) / 2;
			numFatores = nfatores(v);
			i++;
			j++;
		}

		System.out.println(v);
		Calendar c2 = Calendar.getInstance();
		long fim = c2.getTimeInMillis();
		System.out.println("Tempo de execuassão: "+(fim-inicio)+"ms");
	}

	public static int nfatores(int f) {
		int factors = 0;

		for (int i = 1; i <= Math.sqrt(f); i++) {
			if (f % i == 0)
				factors += 2;
		}

		return factors;

	}
cesar.oliveira

Usando a classe cálculo que mostrei acima demorou apenas
Tempo: 125

para descobrir que o primeiro número de triângulo que tem mais de 500 divisores é:

76576500

Abraços galera

Acessem http://programejava.blogspot.com/

WellingtonRamos
Forma bem diferente de obter uma lista de números primos :)
//identifica os número primos menores que 50 000   
    boolean primes[] = new boolean[50000];   
    for (int i = 2; i < primes.length; i++){   
      boolean isPrime = true;   
      for (int j = 2; j <= Math.sqrt(i); j++){   
        if (i % j == 0){   
          isPrime = false;   
          break;   
        }   
      }   
      primes[i] = isPrime;     
         
    }
Criado 16 de junho de 2011
Ultima resposta 17 de jun. de 2011
Respostas 15
Participantes 8