Two Pointers (Dois Ponteiros) em Zig — Implementação e Explicação
A técnica Two Pointers (dois ponteiros) usa dois índices que percorrem um array de forma coordenada, geralmente de extremidades opostas ou na mesma direção. Permite resolver em O(n) problemas que ingenuamente seriam O(n^2).
Como Funciona
Variantes Principais
- Ponteiros opostos: um no início, outro no fim, movem-se para o centro
- Ponteiros na mesma direção: ambos começam no início (fast/slow)
- Ponteiros em duas sequências: um em cada sequência
Visualização
Problema: encontrar par que soma 15 em array ordenado
Array: [1, 3, 5, 7, 8, 10, 12]
L R soma=13 < 15 → L++
L R soma=15 = 15 → encontrado! (3, 12)
Problema: remover duplicatas in-place
Array: [1, 1, 2, 2, 3, 4, 4]
S F 1==1, F++
S F 1!=2, S++, arr[S]=2
S F 2==2, F++
S F 2!=3, S++, arr[S]=3
S F 3!=4, S++, arr[S]=4
S F 3!=4... já copiou
Resultado: [1, 2, 3, 4, ...] (4 elementos únicos)
Implementação em Zig
const std = @import("std");
const Allocator = std.mem.Allocator;
/// Encontra um par de elementos que soma o valor alvo.
/// Array deve estar ordenado. Retorna os índices ou null.
pub fn parComSoma(arr: []const i32, alvo: i32) ?[2]usize {
if (arr.len < 2) return null;
var esq: usize = 0;
var dir: usize = arr.len - 1;
while (esq < dir) {
const soma = arr[esq] + arr[dir];
if (soma == alvo) {
return .{ esq, dir };
} else if (soma < alvo) {
esq += 1;
} else {
dir -= 1;
}
}
return null;
}
/// Remove duplicatas in-place de um array ordenado.
/// Retorna o número de elementos únicos.
pub fn removerDuplicatas(arr: []i32) usize {
if (arr.len <= 1) return arr.len;
var lento: usize = 0;
for (1..arr.len) |rapido| {
if (arr[rapido] != arr[lento]) {
lento += 1;
arr[lento] = arr[rapido];
}
}
return lento + 1;
}
/// Verifica se uma string é palíndromo.
pub fn ehPalindromo(s: []const u8) bool {
if (s.len <= 1) return true;
var esq: usize = 0;
var dir: usize = s.len - 1;
while (esq < dir) {
if (s[esq] != s[dir]) return false;
esq += 1;
dir -= 1;
}
return true;
}
/// Encontra o container com mais água (LeetCode 11).
/// Cada elemento é a altura de uma barra.
pub fn containerComMaisAgua(alturas: []const u32) u64 {
if (alturas.len < 2) return 0;
var esq: usize = 0;
var dir: usize = alturas.len - 1;
var max_agua: u64 = 0;
while (esq < dir) {
const largura: u64 = @intCast(dir - esq);
const altura = @min(alturas[esq], alturas[dir]);
max_agua = @max(max_agua, largura * altura);
if (alturas[esq] < alturas[dir]) {
esq += 1;
} else {
dir -= 1;
}
}
return max_agua;
}
/// Merge de dois arrays ordenados.
pub fn mergeDoisArrays(
allocator: Allocator,
a: []const i32,
b: []const i32,
) ![]i32 {
var resultado = try allocator.alloc(i32, a.len + b.len);
var i: usize = 0;
var j: usize = 0;
var k: usize = 0;
while (i < a.len and j < b.len) {
if (a[i] <= b[j]) {
resultado[k] = a[i];
i += 1;
} else {
resultado[k] = b[j];
j += 1;
}
k += 1;
}
while (i < a.len) {
resultado[k] = a[i];
i += 1;
k += 1;
}
while (j < b.len) {
resultado[k] = b[j];
j += 1;
k += 1;
}
return resultado;
}
/// Three Sum: encontra todos os tripletos que somam zero.
pub fn threeSum(allocator: Allocator, arr: []i32) ![][3]i32 {
var resultados = std.ArrayList([3]i32).init(allocator);
// Ordena primeiro
std.mem.sort(i32, arr, {}, std.sort.asc(i32));
for (0..arr.len) |i| {
if (i > 0 and arr[i] == arr[i - 1]) continue; // pula duplicatas
var esq = i + 1;
var dir = arr.len - 1;
while (esq < dir) {
const soma = @as(i64, arr[i]) + arr[esq] + arr[dir];
if (soma == 0) {
try resultados.append(.{ arr[i], arr[esq], arr[dir] });
while (esq < dir and arr[esq] == arr[esq + 1]) esq += 1;
while (esq < dir and arr[dir] == arr[dir - 1]) dir -= 1;
esq += 1;
dir -= 1;
} else if (soma < 0) {
esq += 1;
} else {
dir -= 1;
}
}
}
return try resultados.toOwnedSlice();
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Par com soma
const arr = [_]i32{ 1, 3, 5, 7, 8, 10, 12 };
if (parComSoma(&arr, 15)) |par| {
try stdout.print("Par com soma 15: arr[{d}]={d}, arr[{d}]={d}\n", .{
par[0], arr[par[0]], par[1], arr[par[1]],
});
}
// Remover duplicatas
var dup = [_]i32{ 1, 1, 2, 2, 3, 4, 4 };
const unicos = removerDuplicatas(&dup);
try stdout.print("Sem duplicatas ({d}): ", .{unicos});
for (dup[0..unicos]) |x| try stdout.print("{d} ", .{x});
try stdout.print("\n", .{});
// Palíndromo
try stdout.print("\"arara\" é palíndromo? {}\n", .{ehPalindromo("arara")});
try stdout.print("\"zig\" é palíndromo? {}\n", .{ehPalindromo("zig")});
// Container com mais água
const alturas = [_]u32{ 1, 8, 6, 2, 5, 4, 8, 3, 7 };
try stdout.print("Max água: {d}\n", .{containerComMaisAgua(&alturas)});
// Merge
const a = [_]i32{ 1, 3, 5, 7 };
const b = [_]i32{ 2, 4, 6, 8 };
const merged = try mergeDoisArrays(allocator, &a, &b);
defer allocator.free(merged);
try stdout.print("Merge: ", .{});
for (merged) |x| try stdout.print("{d} ", .{x});
try stdout.print("\n", .{});
}
Análise de Complexidade
| Problema | Tempo | Espaço |
|---|---|---|
| Par com soma | O(n) | O(1) |
| Remover duplicatas | O(n) | O(1) |
| Palíndromo | O(n) | O(1) |
| Container | O(n) | O(1) |
| Three Sum | O(n^2) | O(1) |
Quando Usar
- Array/lista ordenada (ou pode ser ordenada)
- Problemas envolvendo pares ou subconjuntos
- Quando a solução ingênua usa dois loops aninhados
Recursos Relacionados
- Sliding Window — Técnica complementar para janelas
- Busca Binária — Outra técnica para arrays ordenados
- Merge Sort — Usa merge de dois arrays
- Array Dinâmico — Estrutura base