Two Pointers (Dois Ponteiros) em Zig — Implementação e Explicação

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

  1. Ponteiros opostos: um no início, outro no fim, movem-se para o centro
  2. Ponteiros na mesma direção: ambos começam no início (fast/slow)
  3. 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

ProblemaTempoEspaço
Par com somaO(n)O(1)
Remover duplicatasO(n)O(1)
PalíndromoO(n)O(1)
ContainerO(n)O(1)
Three SumO(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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.