segunda-feira, 23 de dezembro de 2013

Desafio: 25 cavalos

Nesta quadra natalícia, deixo-vos o meu puzzle favorito. Não requer nenhuns conhecimentos especiais, só lógica. Aproveitem e desafiem a vossa família ; )
Tens 25 cavalos e podes fazer corridas de 5 cavalos. Não tens um relógio para medir os tempos, só sabes a ordem dos cavalos em cada corrida. Qual é o número mínimo de corridas necessário para determinar os 3 cavalos mais rápidos?

sexta-feira, 20 de dezembro de 2013

Exemplo de entrevista para Engenheiro de Software na Google

No mês passado, concorri a um estágio de verão na Google. O processo foi relativamente rápido (3~4 semanas), visto que já estagiei na Google no ano passado, e para minha satisfação, recebi a desejada oferta para estagiar no próximo verão em Nova Iorque :)

Para quem estiver interessado em concorrer a uma posição na Google, este post exemplifica uma entrevista para a posição de Engenheiro de Software (Amazon, Apple, Facebook, Microsoft, usam estilos semelhantes). São apresentadas possíveis questões e a forma como eu responderia, para além de alguns comentários sobre o raciocínio (em itálico). Notas prévias:
  1. Esta é apenas a minha visão do funcionamento das entrevistas e não necessariamente a da Google.
  2. Não pretendo, de modo algum, afirmar que esta é a melhor forma de responder a estas perguntas. Estas são simplesmente as respostas que eu daria e o tipo de raciocínio que utilizaria.
  3. Estas perguntas não foram tiradas das minhas entrevistas, mas são exemplos representativos.
Estas entrevistas, quer sejam feitas por telefone ou presencialmente, têm uma duração de 45 minutos e podem ser tipicamente divididas em 4 partes:
  1. Quebrar o gelo
  2. Coding - perguntas para as quais temos de implementar a nossa solução
  3. System Design - perguntas mais genéricas para discussão aberta
  4. Espaço para fazer perguntas ao entrevistador
A única parte obrigatória é a parte 4. De resto, as entrevistas podem tocar em perguntas de cada parte ou focar-se só nas partes 2 ou 3. Uma regra de ouro é não estar muito tempo calado, mas procurar explicar ao máximo aquilo em que estamos a pensar. Caso contrário, o(a) entrevistador(a) não faz ideia se percebemos bem a pergunta ou se estamos com dificuldades.

quinta-feira, 5 de dezembro de 2013

MIUP 2013 - Um problema de Grafos

Não tive oportunidade de escrever no blog nas últimas semanas. Para compensar, hoje vou falar de um problema um pouco mais difícil do o habitual: o problema B da MIUP 2013 - Say Geese.

Objectivo do problema:
Dado um grafo não-dirigido com N (<= 11) vértices e E (<= 45) arestas (edges), quantos subconjuntos de arestas formam um grafo conexo?

segunda-feira, 21 de outubro de 2013

MIUP 2012 Problema C - Um algoritmo ganancioso

A Maratona Inter-Universitaria de Programação (MIUP) 2013 é já no próximo sábado.
A minha última participação foi em 2009 (equipa LuscoFusco) e não tenho seguido com muita atenção as últimas edições da prova. Dada a proximidade da MIUP deste ano, estão disponíveis para treino os problemas de alguns concursos, incluindo os da MIUP 2012. Eu decidi ir resolver os problemas for fun.

Penso que os problemas são equilibrados, com um nível de dificuldade adequado e incluindo alguns problemas com casos particulares para distinguir as equipas com mais atenção ao detalhe. A única critica negativa que faço é ao problema D onde penso que não é claro no enunciado que os carros a colocar num ferry têm de ser consecutivos no input (e isso faz toda a diferença na solução).

O problema que gostei mais foi o problema C - Humanitarian Logistics. Sucintamente, temos N caixas de alimentos, cada uma com um tempo de validade Ti (em dias) e um valor Vi. Em cada dia, podemos escolher uma das caixas cuja validade não tenha expirado para distribuir pela população. O objectivo é minimizar o desperdício, isto é, o valor das caixas cuja validade expira antes de serem entregues, e imprimir os índices (de ordem no input) das caixas que serão entregues por ordem crescente de índice.

Os limites são 1 ≤ N ≤ 100,000;  1 ≤ Ti ≤ N;   1 ≤ Vi ≤ 100,000. Como resolver o problema de forma eficiente?

quarta-feira, 16 de outubro de 2013

Follow-up: número de pares de números cuja soma é <= S

No último post, apresentei algumas abordagens para verificar se existem 2 números num array cuja soma é S. Por fim, sugeri um follow-up: e se fosse para contar o número de pares de números cuja soma é menor ou igual a S?

Para começar, a abordagem O(N2) precisa apenas de uma pequena alteração para contar os pares:

1
2
3
4
5
6
7
8
long long ContaPares(int* valores, int N, int S) {
    long long resultado = 0;
    for (int i = 0; i < N; i++)
        for (int j = i+1; j < N; j++)
            if (valores[i]+valores[j] <= S)
                resultado++;
    return resultado;
}

Note-se que se o array tiver uma dimensão considerável, e.g. ~100 000, o resultado pode exceder facilmente os 32 bits dos ints. Por outro lado, um programa com complexidade 100000 é lento. Só esta função demoraria cerca de 15 segundos no meu computador.

Ordenação ajuda?
Como queremos os pares menores ou iguais a S, ordenar volta a parecer fazer sentido. Voltando ao exemplo dado no último post, o array ordenado fica -3, -2, 1, 5, 6, 7, 10 e S = 12.
Para o número -3, precisamos de saber quantos números existem menores ou iguais a S - (-3), ou seja, 15.
Para -2, seria quantos <= 14,
Para 1, quantos <= 11,
etc.

Se viram os links dados no último post, isto pode ser feito de forma eficiente com pesquisa binária. Assim, basta procurar qual é o índice do primeiro número do array ordenado maior do que S - número_actual e contar quantos números existem entre o número actual e esse índice.
No caso do C++, basta usar a função upper_bound().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
long long ContaParesNlogN(int* v, int N, int S) {
    sort(v, v+N);
    long long resultado = 0;
    for (int i = 0; i < N; i++) {
        // v+i+1 porque procuramos a partir da posição i+1
        // caso não exista nenhum número > S-v[i], pos = N
        int pos = upper_bound(v+i+1, v+N, S-v[i]) - v;
        resultado += pos - (i + 1);
    }
    return resultado;
}

Temos a ordenação e é feita uma pesquisa binária para cada um dos N números. Assim, o tempo de execução é O(N log N).

De modo análogo ao problema anterior, podemos voltar a eliminar a pesquisa binária. Se -3 + 10 for maior do que S, então o 10 não vai formar um par válido com nenhum outro número e pode ser descartado.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
long long ContaParesNlogNv2(int* v, int N, int S) { 
    sort(v, v+N);
    long long resultado = 0;    
    int a = 0, b = N-1;
    while (a < b) {
        // descarta números que formam pares inválidos.
        while ((b > a) && (v[a] + v[b] > S))
            b--;
        // conta os pares (a,a+1), (a,a+2), ... (a,b) = b - a pares
        resultado += b - a;
        a++;
    }
    return resultado;
}

O ciclo while exterior tem, no máximo, N-1 iterações. Cada iteração do ciclo while interior descarta um número do fim do array e retira uma iteração ao ciclo exterior porque decrementa b. Assim, o tempo de execução é O(N). Contudo, o tempo total continua dominado pelo O(N log N) da ordenação.

E hashing?
Como vimos no post anterior, as hash tables são muito úteis. No entanto, não podem ser usadas em todas as situações. Para contar o número de pares de forma eficiente, precisamos de usar a ordem dos números. As hash tables são excelentes para inserir, eliminar ou pesquisar elementos, mas não nos dizem nada sobre a ordem dos números e são inúteis para este problema.

O(N) é possível?
No caso de arrays já ordenados, sim. Basta usar o método referido acima.
Para arrays não ordenados, estamos limitados pela ordenação. No caso de termos números inteiros, temos:
  • Counting Sort: pode ser utilizado quando sabemos que os números estão contidos num intervalo pequeno (e.g. entre 0 e 1000, -100 e 100 ou 1000 e 9999).
  • Radix Sort: depende do número de bits K necessário para representar todos os números e é bastante útil em algumas situações. O tempo de execução é O(K * N) e é considerado linear se K for considerado uma constante (K é independente do valor N).

domingo, 13 de outubro de 2013

Dados N números inteiros, existem 2 números cuja soma é igual a um dado número S?

No último post, usei o seguinte problema como exemplo:
Dados N números inteiros, existem 2 números cuja soma é igual a um dado número S?
A solução mais óbvia é testar todos os pares de números e foi demonstrado que o seguinte algoritmo tinha um tempo de execução O(N2).

1
2
3
4
5
6
7
bool VerificaPares(int* valores, int N, int S) {
    for (int i = 0; i < N; i++)
        for (int j = i+1; j < N; j++)
            if (valores[i]+valores[j] == S)
                return true;
    return false;
}

A notação Ó-grande significa que o tempo de execução do algoritmo é no máximo proporcional a N2.
Agora o desafio é: como fazer melhor para podermos suportar mais de 10000 números?

sexta-feira, 11 de outubro de 2013

A importância dos algoritmos para um engenheiro de software

Estou muito satisfeito por estar a ajudar a renovar as equipas que vão representar a FEUP nos concursos de programação da ACM. Há muitos alunos novos que estão a ver este "mundo" pela primeira vez e foi giro ver a sua reacção no concurso interno que fizemos esta semana :-)

No entanto, apesar deste interesse inicial dos alunos, uma pergunta natural é: porque é que isto é importante? Ou, por outras palavras, porque é que eles hão de despender parte do seu tempo livre a aprender mais sobre algoritmos, estruturas de dados e tudo o resto que está relacionado com os concursos?

Parte da resposta já foi dada noutros sítios. Neste post vou apenas realçar algumas das razões.
Eu sugiro a leitura do tutorial do TopCoder sobre a importância dos algoritmos e o artigo da Academia ONI sobre a análise de algoritmos.

Antes de mais.. o que é um algoritmo?
Um algoritmo pode ser definido como uma sequência de passos que transforma um dado input (um valor ou conjunto de valores) no resultado pretendido - output - sendo este tipicamente um valor ou conjunto de valores.
Na prática, um engenheiro de software gasta grande parte do seu tempo a implementar algoritmos. Para cada parte do projecto de desenvolvimento de software, existem um conjunto de desafios e é necessário definir qual vai ser a abordagem para os ultrapassar. Estes desafios podem ser coisas simples como ler um ficheiro de texto e apresentar a informação ao utilizador ou coisas mais avançadas como o processamento de giga, tera ou mais bytes de dados para determinar alguma estatística.

Análise do tempo de execução de algoritmos
Imaginem que têm este problema: dados N números inteiros, existem 2 números que somados dão um número S?
Uma solução natural é testar todos os pares de números e verificar se a sua soma dá S. Exemplo em C++:

1
2
3
4
5
6
7
bool VerificaPares(int* valores, int N, int soma) {
    for (int i = 0; i < N; i++)
        for (int j = i+1; j < N; j++)
            if (valores[i]+valores[j] == soma)
                return true;
    return false;
}

O código é simples e tudo indica que dá a resposta certa. No entanto, um factor importante na vida real é saber se esta performance chega para as nossas necessidades. Qual é a quantidade máxima de números que este algoritmo suporta se o nosso programa tiver no máximo 1 segundo para executar?

Para responder a esta pergunta, é necessário analisar o tempo de execução do algoritmo apresentado. A notação mais popular para representar o tempo de execução (ou complexidade temporal) é a notação Ó-grande e é expressa em função do tamanho do input. Neste caso, é em função do número N de elementos do array.

Para cada número do array (ciclo i), é verificada a sua soma com todos os outros números (ciclo j). Assim, para cada iteração do ciclo i, o ciclo j tem N-1, N-2, N-3, .., 1, 0 iterações.
O número total de iterações é a soma dos números [0, N-1] que é a soma da conhecida progressão aritmética de passo 1, igual a N*(N-1)/2 = (N2 - N) / 2. Na definição da complexidade temporal do algoritmo, só utilizamos o valor mais significativo e ignoramos as constantes. Assim,
  • O((N- N) / 2) = O(N2 / 2), ignoramos o factor N porque este é dominado pelo factor N2
  • O(N/ 2) = O(N2), ignoramos o factor 1/2 visto que é uma constante.
Deste modo, conseguimos uma definição compacta para a complexidade temporal do algoritmo proposto (ler os links dados para mais detalhes): O(N2). O processo pode parecer complexo ou envolvendo muita matemática mas na verdade é tipicamente muito simples porque ignoramos as constantes e outros factores dominados (e.g. o N acima).

Agora que sabemos qual é o tempo de execução, falta-nos estimar o N máximo para que o programa execute no máximo em 1s. O meu portátil tem um processador com frequência de 2.53 GHz, isto significa que o processador executa cerca de 2.53 * 10instruções por segundo. No entanto, o processador não está 100% dedicado ao nosso programa e aquilo que programamos é tipicamente traduzido em mais do que uma instrução do processador.

A experiência diz-nos que uma estimativa razoável é considerar que 100 milhões de operações são executadas em 1 segundo. Como o nosso programa tem um tempo de execução O(N2), o nosso programa pode processar √108 = 10000 números em 1s. A tabela seguinte contém algumas estimativas do tempo de execução em função do tamanho do input (ver análise de algoritmos).
Nota: como diz no artigo, estes tempos consideram apenas ciclos que demoram um determinado tempo, ignorando o resto do programa. Daí a diferença de 1s para 0.1s, 1s é uma estimativa mais conservadora.

Exercício para o leitor: a solução apresentada é propositadamente ineficiente. Como fazer melhor do que 2 ciclos encadeados? Isto é, como fazer melhor do que O(N2)?

Impacto na vida real e no meu estágio na Google
Não há necessidade de reinventar a roda nestes posts. Volto a sugerir a leitura dos links referidos para obtenção de mais informação. Existem diversos tipos de algoritmos e conhecimento é poder.
Este conhecimento de determinados algoritmos ou técnicas pode permitir transformar uma ideia que parecia impossível (como vimos acima, um algoritmo poderia demorar centenas de anos ou mais até dar a resposta) numa feature bem real e popular que torna a nossa aplicação melhor do que a concorrência.

Muitos dos problemas dos concursos de programação são apresentados via uma história que pode parecer irrealista. No entanto, os algoritmos usados para resolver estes problemas são úteis para as situações que aparecem na vida profissional de um engenheiro de software.

As skills desenvolvidas ao participar nos concursos de programação são úteis no desenho e implementação de soluções para os desafios do software. Não só a parte dos algoritmos, mas também a capacidade de trabalho sob pressão, debugging e coding skills. Com a experiência destes concursos, e lendo soluções de outros concorrentes se possível (e.g. TopCoder, CodeChef), nós aprendemos a estruturar os nossos algoritmos da forma mais simples possível, resultando tipicamente em código bastante sucinto e eficiente.

Eu já resolvi alguns milhares de problemas online, seja em treino, seja por hobby. Assim, já enfrentei desafios muito diversos e também problemas semelhantes mas com restrições ou enquadramentos diferentes.
Deste modo, o meu cérebro foi treinado ao longo do tempo para analisar e identificar rapidamente quais são os requisitos e as restrições verdadeiramente importantes de diversas situações, seguido-se a identificação da solução mais apropriada para cada situação.
A consequência disto foi estar à vontade durante as entrevistas e conseguir um estágio na Google. Além disso, quer se queira, que não, parte do desenvolvimento de software é pouco desafiante e/ou repetitivo. A experiência adquirida ao resolver este tipo de exercícios permitia-me resolver as minhas tarefas muito rapidamente porque estava (tipicamente) perante problemas (mais fáceis ou mais difíceis) que já tinha enfrentado antes. Daí o ganho na produtividade.
Frequentemente, os meus superiores tinham de me arranjar outros projectos para me manter ocupado ("I have to find something to keep this guy busy"), enquanto que eles tentavam arranjar tempo para me fazerem code reviews :-)

quarta-feira, 25 de setembro de 2013

Desafio: bolas brancas e pretas

Os primeiros posts têm de ser um pouco mais genéricos para dar alguma base para o futuro.
Para quebrar um pouco esta rotina inicial, sugiro um desafio inspirado num problema do SPOJ e que pode ser feito a qualquer pessoa.

Desafio: bolas brancas e pretas

Imaginem que têm uma caixa de mágico com um certo número de bolas brancas e pretas.
Enquanto a caixa contém mais do que uma bola, retiram aleatoriamente 2 bolas da caixa:

  • Se as 2 bolas retiradas têm a mesma cor, então colocam uma bola branca na caixa 
  • Se as 2 bolas tiverem cores diferentes, então colocam uma bola preta

Em ambos os casos, as 2 bolas retiradas não voltam para a caixa. Assim, dado o número inicial de bolas brancas e bolas pretas, qual é a cor da bola que resta na caixa?

Exemplos:

  • 1 bola branca e 1 bola preta. As 2 bolas retiradas têm cores diferentes, é colocada uma bola preta. Só sobra 1 bola preta.
  • 2 brancas e 0 pretas. Retiram-se 2 bolas brancas e coloca-se 1 bola branca na caixa.
  • 2 brancas e 1 preta. Se retirarmos primeiro as 2 bolas brancas, ficamos com 1 bola branca e 1 preta na caixa. Como vimos acima, o resultado é uma bola preta. Se começarmos por retirar 1 branca e 1 preta, adiciona-se 1 preta e voltamos a ter 1 branca e 1 preta. Em qualquer caso, o resultado é ficarmos com 1 bola preta.

E se fossem 5555555 bolas brancas e 5555555 pretas? Ou 10000000 brancas e 10000000 pretas?
Conseguem descobrir o truque para responder a qualquer caso? :-)



quinta-feira, 19 de setembro de 2013

Sou o maior da minha aldeia, mas.. isso significa alguma coisa?

Já conheço o CodeChef há bastante tempo, mas só nas últimas férias, em Agosto, decidi participar no seu concurso mensal. Estes concursos mensais decorrem no inicio de cada mês, contêm 10 problemas que os concorrentes podem tentar resolver durante os 10 dias de prova.

Após 2 concursos, Agosto e Setembro, já sou o #1 português do ranking de concursos longos (os de 10 dias). Sim, sou o maior da minha aldeia, mas.. na prática .. isso significa alguma coisa?

Por um lado, a minha participação é puramente por hobby, for fun. No concurso de Setembro, dediquei algumas horas (de 1 dos 10 dias) ao concurso e resolvi 6 dos 10 problemas.
Por outro lado, e mais importante do que isso, só há 14 portugueses a participar no CodeChef. Além disso, não se sabe se eles estão a dedicar-se mais ou ainda menos do que eu. Mais ainda, não se sabe se são muito bons, medianos ou medíocres.

Este é apenas um exemplo do quão enganosos os números podem ser. Na prática, estes números não significam nada.
Eu cheguei a ter colegas na escola que só decidiam se ficavam satisfeitos ou tristes com uma nota depois de saber as notas dos outros. Esta mentalidade é muito comum e é um grande erro. Nós devemos aproveitar o contacto com as pessoas à nossa volta para aprender o máximo possível, mas estas pessoas não devem nunca definir os nossos limites ou os nossos objectivos.

Por todos estes motivos, o meu lema nunca foi tentar ser o melhor da turma ou da minha aldeia, mas sim tentar sempre aprender mais e melhorar as minhas competências. Usando uma analogia do futebol, o que acham melhor: ser o melhor jogador a actuar nos campeonatos distritais ou fazer parte da equipa campeã nacional?

Citando William Faulkner,

Always dream and shoot higher than you know you can do. 
Don't bother just to be better than your contemporaries or predecessors.
Try to be better than yourself.

quarta-feira, 18 de setembro de 2013

Preparação para concursos de programação

Estamos no início do ano lectivo e isso também significa que a nova temporada de concursos de programação (mais escolares) também está prestes a iniciar-se.

Os alunos do ensino básico e secundário são elegíveis para as Olimpíadas Nacionais de Informática (ONI) e têm a hipótese de serem seleccionados para representar Portugal nas Internacionais, International Olympiad in Informatics (IOI) 2014, em Taiwan. Por seu turno, os alunos universitários poderão competir na Maratona Inter-Universitária de Programação (MIUP) já em Outubro, de modo a qualificarem-se para o South Western European Regional Contest (SWERC), concurso da nossa zona europeia que permite o acesso à final mundial do International Collegiate Programming Contest (ICPC) da ACM.

Independentemente do nível de ensino, a pré-condição fundamental para participar é saber programar. A maioria dos concursos suportam as linguagens C, C++, Java e Pascal. Assim, o primeiro passo é escolher uma delas, estudá-la e praticar para se ser tão proficiente quanto possível.
O C++ é a linguagem mais popular e é a minha linguagem favorita. No entanto, um dos "erros" mais comuns é a falta de conhecimento da Standard Template Library, particularmente os diversos algoritmos já disponíveis na biblioteca algorithm (sort, lower_bound, upper_bound, next_permutation, etc).

Os problemas colocados nestas provas devem ser resolvidos com um programa desenvolvido pelos concorrentes. Para além destes programas serem obrigados a dar as respostas correctas, existem também restrições quanto ao tempo disponível para calcular as respostas e à memória (do computador) disponível.
Assim, os concorrentes devem encontrar as ideias certas para resolver os problemas de forma eficiente, isto é, encontrar o Algoritmo certo para resolver os problemas e implementá-lo usando uma linguagem de programação.

Infelizmente, não conheço muitos recursos em português apropriados para a aprendizagem de algoritmos. As ONI criaram um espaço chamado Academia ONI que contém alguns artigos: "Noções Básicas de Análise de Algoritmos", "Tipos de Problemas" comuns em concursos e "Pesquisa Completa" de soluções. O forum Portugal-a-Programar contém uma secção de Algoritmia e Lógica, da qual recomendo um artigo sobre Grafos.

Dada esta escassez, convém dominar o inglês e explorar tanto os livros como a internet.
A Ciência da Computação é uma área muito vasta. Existem diversos tipos de algoritmos diferentes. Este conhecimento está tipicamente melhor estruturado nos bons livros:

  • O meu preferido é o Introduction to Algorithms. Este pode ser considerado uma bíblia e é um pouco difícil de seguir mas é um recurso fenomenal.
  • Programming Challenges é um livro escrito especialmente para concursos e é encontrado facilmente online. 
  • Na disciplina de algoritmos na faculdade, era recomendado o Data Structures and Algorithm Analysis in C++, este não é tão completo mas o autor é bastante claro nas suas explicações. 
  • Outro livro muito recomendado é o Algorithm Design Manual, mas não o conheço.

Contudo, muitas vezes, quem está a começar nestas coisas não tem ainda vontade de estudar um livro, especialmente os mais jovens (eu também não tinha!). Existem outros tipos de recursos:

  • programa de treino da USACO é uma plataforma de treino para as olimpíadas de informática. Não conheço melhor recurso online para aprender algoritmos e estruturas de dados. Está dividido por módulos que contêm tanto textos de apoio como exercícios para aplicar os conhecimentos transmitidos nesses textos.
  • Tutoriais curtos e objectivos. Eu recomendo vivamente os artigos referidos anteriormente em português, assim como os tutoriais do TopCoder.
  • Existem cada vez mais cursos online. A disciplina de desenho e análise de algoritmos da Universidade de Stanford é oferecida no Coursera, estando dividida em parte 1 e parte 2.

À medida que os conhecimentos são adquiridos e fortalecidos, é importante exercitar estes conhecimentos. Existem diversos judges online como o Sphere Online Judge. Estes judges disponibilizam um conjunto vasto de problemas e os utilizadores podem resolve-los livremente como treino.
Além dos judges, existem outros sites como o TopCoder, o Codeforces e o CodeChef que organizam competições de treino (normalmente há pelo menos 1 por semana) e é publicado um editorial depois de cada concurso que explica como se poderiam resolver os exercícios.

O post já vai longo, mas já deu certamente para perceber uma coisa: é preciso trabalho, trabalho, trabalho. Tal como em tudo na vida, não existem fórmulas mágicas. No entanto, quem corre por gosto, não cansa :-)

sábado, 14 de setembro de 2013

Olá Mundo

Cara world wide web,

Tal como em outras situações na minha vida, aqui estou eu a fazer algo que nunca pensei que faria: a iniciar um blog. O meu nome é Miguel Oliveira, tenho um mestrado em engenharia informática e sou aluno de doutoramento na Faculdade de Engenharia da Universidade do Porto.

Desde cedo que as aulas foram um aborrecimento - tudo demasiado fácil - as quais culpo, em parte, pelo meu vício de roer as unhas :-) No entanto, no 10º ano aprendi a programar e surgiu desde logo a paixão pela resolução de desafios com recurso à programação. Desde então ganhei vários concursos, medalhas de ouro e bronze nos concursos da nossa zona europeia do International Collegiate Programming Contest e fiquei entre o top 500 do Google Code Jam 2009.

Pelo caminho, já resolvi mais de 1000 problemas online (na verdade, já perdi a conta mas dá para ter uma ideia), sendo neste momento o 29º do ranking mundial do Sphere Online Judge (em mais de 100 000 utilizadores). Estes desafios continuam a ser um hobby e continuo a participar em forums online como o Portugal-a-Programar ou o do TopCoder, onde procuro ajudar quem está com dificuldades em algum exercício.

No ano passado fiz um estágio como Software Engineer na Google e foi uma experiência muito enriquecedora. Há algumas semanas, um rapaz que estava em entrevistas para a Google pediu-me umas dicas e lançou a ideia de criar um blog com conteúdos em português. De facto, foi algo que me fez muita falta quando estava a começar nestas coisas. Ele tinha em mente algo mais a sério, mas este blog pretende transmitir alguns dos conhecimentos que fui adquirindo ao longo do tempo, assim como apresentar desafios que considere interessantes quer para participantes em concursos, quer para desafiar os amigos em conversa de café :-)

Miguel Oliveira

PS: espero que este blog também ajude a melhorar a proficiência na escrita, que é algo que bem preciso...