Desafio de programação em entrevista

Olá pessoal, há alguns dias participei do processo seletivo de uma empresa estrangeira e fiquei um tanto intrigado com uma etapa de programação prática.

Em determinado momento da entrevista, o recrutador me enviou o seguinte link e pediu que eu compartilhasse minha tela:

Pois bem, após o entendimento do problema, ele me solicitou que eu implementasse o algoritmo para encontrar a maior área possível para os parâmetros informados, porém eu só poderia utilizar um único laço.

Como o problema envolve analizar pares de “paredes” para fazer o cálculo, só consegui pensar em algoritmos utilizando 2 laços aninhados.
Acabei falando para o cara que estou desistindo da vaga pois não consigo imaginar uma forma de calcular sem fazer 2 laços aninhados.

Alguém tem uma ideia de resolver de outra forma, mais otimizada?

Meses atrás teve outra empresa estrangeira onde a prova tinha mais de 50 questões e 15 minutos para ser resolvida, mas eram questões desde literatura, lógica, psicotécnico até trechos de código para serem implementados.
Lembro que e fiz o cálculo e pelo tempo da prova, eu tinha 18 segundos pra resolver cada questão.

O que vocês acham desse tipo de “prova” durante uma entrevista?
Pra mim não faz sentido, nas empresas ninguém chega com uma demanda para ser implementada em 5 minutos.

3 curtidas

Problema interessante. A plataforma lá tem uma área com soluções, pode ser que tenha um exemplo com 1 laço.

Com 2 laços também sinto que a solução é relativamente simples, mas com 1 laço só, eu precisaria de um bocado de tempo pra pensar em algo, o que seria estranho em uma entrevista. Ficar 2h batendo cabeça enquanto o entrevistador assiste não me parece ideal, e um desperdício de tempo pra ambos.

Não acho que seja algo ruim um tipo de teste desse, o maior problema é saber o que o entrevistador espera que você obtenha. Em teoria, ele poderia ver como você desenvolve a lógica e, mesmo que não chegue à um resultado, entender como você programa. Mas também ocorre de ele simplesmente esperar uma solução que ele já conhece (por ter decorado), e descartar quem não chegue perto dessa solução.

Mesmo caso acima: o problema maior é saber o que se espera de um teste desses. Um bom entrevistador não olharia se o entrevistado respondeu tudo, mas quais perguntas você escolheu responder, como as respondeu, e ter uma ideia melhor do seu perfil por aí. Mas é fácil imaginar o recrutador esperando todas as perguntas respondidas, ou um percentual fixo de respostas corretas.

Pelo que leio por aí, recrutamento em muitas empresas estrangeiras tem esse tipo de coisa, com múltiplas etapas, às vezes tomando dias. Nesse quesito, ao menos as empresas brasileiras parecem mais eficientes (ou menos burocráticas).

Abraço.

3 curtidas

A ideia é escanear de fora para dentro, diminuindo o intervalo sempre baseado na menor parede entre as duas. Você mantém dois inteiros para marcar a posição das paredes levadas em consideração em um determinado momento. Enquanto esses “ponteiros” não se sobrepõe, calcula-se a altura das duas paredes que eles estão marcando, pega-se a menor e multiplica-se pela distância entre as duas paredes, assim você tem a área do retângulo naquele momento e, caso ela seja uma área maior do que a já encontrada (se for o caso), atualiza como maior área. Se a parede menor foi a da esquerda, avança o ponteiro da esquerda. Se a parede menor foi a da direita, retrai o ponteiro da direita. Segue minha solução. Foi aceita.

class Solution {
    public int maxArea(int[] height) {

        int esquerda = 0;
        int direita = height.length - 1;

        int altEsquerda;
        int altDireita;

        int maiorArea = 0;
        int area;

        while ( esquerda < direita ) {

            altEsquerda = height[esquerda];
            altDireita = height[direita];

            area = Math.min( altEsquerda, altDireita ) * ( direita - esquerda );

            if ( maiorArea < area ) {
                maiorArea = area;
            }

            if ( altEsquerda < altDireita ) {
                esquerda++;
            } else {
                direita--;
            }

        }

       return maiorArea;

    }

}
6 curtidas

Eu acho q eu não ia conseguir completar o teste a tempo. E nem se estresse com isso pois esses processos de entrevistas não funcionam (mas é o que eles têm pra hoje). Se funcionassem, não existiria contratação mal feita.

E pra fazer com um loop só, acho q só utilizando de funções e OO (que internamente podem ter os loops embutidos).

Oi David, sua solução é boa, mas os seus dois extremos estão andando junto. Se tivermos dois números bem altos em uma das extremidades acho que seu código não retornaria a resposta correta. Exemplo:

[1,8,6,2,5,4,8,300,700]

PS: não testei, posso estar enganado.

Repensando, recursão pode ser uma opção. Dá pra argumentar que não é um laço. :grin:

Isso levanta um ponto interessante: a plataforma espera um resultado correto generalizado, ou só para o conjunto de dados do exemplo?

Mais importante: o recrutador que está assistindo sabe disso? Ele saberia diferenciar um código que funciona para diferentes casos, de um que só passa o teste da plataforma?

Por isso considero que, sem saber o que o recrutador espera, tudo fica mais nebuloso ainda.

Abraços.

1 curtida

Note que a resposta gerada é baseada na “exaustão” das possibilidades, sempre tentando maximizar o ganho. A solução passa na plataforma mencionada. Caso haja duas paredes iguais, a da direita vai ganhar, mas poderia ser o contrário sem problema ( altEsquerda <= altDireita ) se é isso que vc apontou.

Recursão no final das contas é um processo iterativo que ao invés de usar um contador/sentinela/flag seja lá o nome que vc quiser dar, usa a pilha de invocação para “controlar” o processo de repetição que deve cessar em um ou mais casos base ou repetir em um ou mais casos recursivos/indutivos/recorrentes. Claro, recursão para os “não iniciados” parece um bicho de sete cabeças, ainda mais quando você quer, a partir do caso base alcançado em uma cadeia mais profunda de chamadas recursivas, que o valor gerado no caso em questão seja propagado para a base da pilha. É difícil de enxergar o caminho dos retornos eu quero dizer.

Como curiosidade, uma solução recursiva seria assim (também testei na plataforma e foi aceita):

class Solution {
    
    private static class MaiorArea {
        int valor;
    }

    public int maxArea(int[] height) {
        MaiorArea maiorArea = new MaiorArea();
        maxAreaRec( height, 0, height.length - 1, maiorArea );
        return maiorArea.valor;
    }

    private void maxAreaRec( int[] height, int esquerda, int direita, MaiorArea maiorArea ) {

        if ( esquerda < direita ) {

            int altEsquerda = height[esquerda];
            int altDireita = height[direita];

            int area = Math.min( altEsquerda, altDireita ) * ( direita - esquerda );

            if ( maiorArea.valor < area ) {
                maiorArea.valor = area;
            }

            if ( altEsquerda < altDireita ) {
                maxAreaRec( height, esquerda + 1, direita, maiorArea );
            } else {
                maxAreaRec( height, esquerda, direita - 1, maiorArea );
            }

        }

    }

}

Como não temos ponteiros em Java, uma solução para armazenar o valor é usar uma classe interna como eu fiz. Vou tentar fazer sem o uso da classe. Se tiver sucesso atualizo aqui.

Normalmente essas plataformas esperam resultados generalizados e sempre tem os “corner cases”/casos patológicos para atrapalhar. Sobre o recrutador, provavelmente ele resolveu o problema antes. Eu seleciono alunos para monitoria usando desafios de programação que se encaixam no que eles já conhecem. É super efetivo.

ATUALIZAÇÃO

Segue a solução recursiva sem a classe interna:

class Solution {

    public int maxArea(int[] height) {
        return maxAreaRec( height, 0, height.length - 1, 0 );
    }

    private int maxAreaRec( int[] height, int esquerda, int direita, int maiorArea ) {

        if ( esquerda < direita ) {

            int altEsquerda = height[esquerda];
            int altDireita = height[direita];

            int area = Math.min( altEsquerda, altDireita ) * ( direita - esquerda );

            if ( maiorArea < area ) {
                maiorArea = area;
            }

            if ( altEsquerda < altDireita ) {
                return maxAreaRec( height, esquerda + 1, direita, maiorArea );
            } else {
                return maxAreaRec( height, esquerda, direita - 1, maiorArea );
            }

        }

        return maiorArea;

    }

}

Note também que sempre que for possível se usar estruturas iterativas ao invés de recursão, procure usar iteração, visto que com recursão a ordem de complexidade relativa ao espaço vai fatalmente piorar. Na versão iterativa desse problema, temos algo como O(1), enquanto na recursiva temos O(n). Em relação ao tempo, ambas são O(n). Como comparativo, nas minhas três submissões eu obtive os seguintes resultados nos tempos de execução:

  • Iterativo: 3ms
  • Recursivo com a classe interna: 16ms
  • Recursivo sem a classe interna: 13ms

No uso de memória, também como se espera, a versão iterativa usou 10MB a menos. Pelo uso de memória e pela quantidade de tempo, é quase certeza que são executados diversos testes ou então um teste bem grande, visto que o domínio da função varia de 2 a 10^5 e as alturas variam de 0 a 10^4.

3 curtidas

Olhando melhor, acho que sua primeira solução já resolve o problema sim. Pois os extremos não andam junto como eu disse, e sim separadamente.

Resolve, pois passa no teste da plataforma, como eu já disse.

Totalmente fora da realidade.
Detesto esse tipo de prova

1 curtida

Também penso assim, são situações tão hipotéticas que não chegam nem perto do dia a dia real de uma empresa.