Two Pointers
Escrito por: Leonardo Paes
Two Pointers é uma técnica muito utilizada para a resolução de problemas de programação competitiva. A ideia consiste em usar dois ponteiros, e , que representam a posição mais a esquerda de um intervalo, o , e a posição mais a direita, o . Dados dois ponteiros, calculamos a resposta para esse intervalo e, baseado em alguma condição que varia de problema em problema, escolhemos o próximo intervalo a ser observado. Para isso, podemos incrementar ou decrementar algum dos dois ponteiros.
Em certos casos, como no exercício 1, usaremos essa técnica apenas para representar duas posições de um vetor, ao invés de um intervalo.
Exercício 1:
Dado um vetor , ordenado em ordem crescente, de inteiros, descubra se existe algum par de elementos tal que sua soma seja igual a .
Restrições: e , sendo uma posição válida do vetor .
Solução Lenta: Uma possível e intuitiva abordagem consiste em percorrer o vetor inteiro e, para cada posição do vetor, percorrê-lo por inteiro novamente, checando para cada dupla de elementos encontrados, se a sua soma é igual a . Infelizmente, tal algoritmo tem a complexidade , o que significa que o código não é bom o bastante.
Código de exemplo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Noic - Ideia 4 | |
// Exercício 1 | |
// Complexidade: O(N^2) | |
// Por Leonardo Paes | |
#include <bits/stdc++.h> | |
using namespace std; | |
int v[1000100]; | |
int main(){ | |
int n, x; | |
cin >> n >> x; | |
for(int i=1; i<=n; i++){ | |
cin >> v[i]; | |
} | |
bool resposta=false; | |
for(int i=1; i<=n; i++){ | |
for(int j=1; j<=n; j++){ | |
if(v[i]+v[j]==x){ | |
resposta=true; | |
} | |
} | |
} | |
cout << resposta << endl; | |
return 0; | |
} |
Solução Rápida: Agora, note que o vetor está ordenado. Mas em que, exatamente, isso pode nos ajudar? Simples! Vamos começar com nosso ponteiro na primeira posição do vetor e com na última posição do vetor. Então, se , encontramos uma soma que satisfaz o problema! Mas e se ? Há dois casos a serem analisados: Ou , ou .
Primeiro Caso: Como o vetor está ordenado crescentemente, ao incrementarmos em 1 o nosso , a soma será maior do que a soma anterior!
Segundo Caso: Analogamente, ao decrementarmos em 1 o nosso , a soma será menor do que a soma anterior!
Após efetuarmos as operações necessárias de um dos dois casos, basta checarmos se a soma atual . Se ela for, encerramos o algoritmo, de forma contrária, continuaremos a escolher um dos dois casos, dependendo da soma atual encontrada! Devemos interromper o algoritmo caso , pois, se , estaríamos olhando apenas uma posição, e não duas.
Código da Solução:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Noic - Ideia 4 | |
// Exercício 1 | |
// Complexidade: O(N) | |
// Por Leonardo Paes | |
#include <bits/stdc++.h> | |
using namespace std; | |
int v[1000100]; | |
int main(){ | |
int n, x; | |
cin >> n >> x; | |
for(int i=1; i<=n; i++){ | |
cin >> v[i]; | |
} | |
bool resposta=false; | |
for(int l=1, r=n; l<r;){ | |
if(v[l]+v[r]==x){ | |
resposta=true; | |
break; | |
} | |
if(v[l]+v[r]<x){ | |
l++; | |
} | |
if(v[l]+v[r]>x){ | |
r--; | |
} | |
} | |
cout << resposta << endl; | |
return 0; | |
} | |
Exercício 2:
Seja um vetor cíclico de posições. Um vetor cíclico é um vetor tal que a posição que vem após a última é a primeira, ou seja, . Dado um inteiro , diga o número de intervalos , tal que a soma de todos os elementos do vetor correspondentes a esse intervalo possui soma igual a . Ou seja: . Note que, como o vetor é circular, intervalos em que são intervalos válidos. Dois intervalos são considerados diferentes se seus respectivos inícios ou fins são diferentes.
Restrições: e . , sendo uma posição válida do vetor .
Solução : Inicialmente, vamos resolver o problema do vetor ser cíclico. Para isso, a ideia é de apenas duplicar o vetor! Isso mesmo, enquanto estamos lendo a entrada, basta falarmos que . Agora, utilizaremos a ideia dos Two Pointers. Inicializaremos com: O ponteiro , com o ponteiro e com o inteiro , o que significa que estamos analisando o intervalo . Então, de forma similar a do exercício 1, se nós aumentaremos nossa resposta em 1. Se , há dois casos a serem analisados: Ou , ou .
Primeiro Caso: Como queremos aumentar a soma dos números, incrementaremos em 1, pois, assim, teremos um número a mais acrescentando na nossa soma. A soma deve ser acrescentada em .
Segundo Caso: Já que a soma atual está grande demais, devemos diminuir nosso intervalo, então, incrementaremos em 1, diminuindo em 1 o tamanho do intervalo. Note que antes de incrementarmos, devemos retirar da soma, pois ele não pertence mais ao intervalo.
Agora, se , encontramos mais um intervalo válido.Neste caso, incrementaremos ambos quanto o , para prosseguirmos para o próximo intervalo. Caso contrário, devemos continuar o algoritmo, e escolher entre os dois casos, de acordo com a soma atual. Lembre-se de que o vetor só tem posições, então, os nossos intervalos contados devem ter, no máximo, elementos. Assim, devemos checar se . Se isso ocorrer, devemos aumentar o em 1, para diminuirmos o tamanho do intervalo. Enquanto , continuaremos o algoritmo. Se , devemos interromper o loop, pois começaríamos a recontar intervalos, já que .
Código de Solução:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Noic - Ideia 4 | |
// Exercício 2 | |
// Complexidade: O(N) | |
// Por Leonardo Paes | |
#include <bits/stdc++.h> | |
using namespace std; | |
int v[3000000]; | |
int main(){ | |
int n, x, resposta = 0, soma; | |
cin >> n >> x; | |
for(int i = 1; i <= n; i++){ | |
cin >> v[i]; | |
v[n+i]=v[i]; | |
} | |
soma=v[1]; | |
for(int l=1, r=1; l<=n;){ | |
if(r-l>n){ | |
soma-=v[l]; | |
l++; | |
} | |
else if(soma<x){ | |
r++; | |
soma+=v[r]; | |
} | |
else if(soma>x){ | |
soma-=v[l]; | |
l++; | |
} | |
else if(soma == x){ | |
resposta++; | |
r++; | |
soma+=v[r]; | |
soma-=v[l]; | |
l++; | |
} | |
} | |
cout << resposta << endl; | |
} |
PROBLEMAS PARA PRATICAR:
- Sanduíche (OBI 2016 - P2 - F1).
- Hottest (Practice Task IOI 2011).
- Cities Robbery (CS Academy).
- Balanced Team (Codeforces - Div3 C).
- Three Parts of the Array (Codeforces - Div3 C).
- BerSU Ball (Codeforces - Div2 B).
- Desafio: Jury Meeting (Codeforces - Div1 B).