LIS (Maior Subsequência Crescente) em Zig — Implementação e Explicação
O algoritmo LIS (Longest Increasing Subsequence) encontra a maior subsequência estritamente crescente em uma sequência de números. A solução clássica de DP é O(n^2), mas existe uma versão otimizada com busca binária em O(n log n).
Como Funciona
Versão O(n^2)
dp[i] = comprimento da LIS que termina no elemento i.
dp[i] = 1 + max(dp[j]) para todo j < i onde arr[j] < arr[i]
Versão O(n log n) — Patience Sorting
Mantém uma lista tails onde tails[i] é o menor elemento final possível para uma subsequência crescente de comprimento i+1.
Visualização
Array: [10, 9, 2, 5, 3, 7, 101, 18]
DP (O(n²)):
i=0: dp[0]=1 (10)
i=1: dp[1]=1 (9)
i=2: dp[2]=1 (2)
i=3: dp[3]=2 (2,5)
i=4: dp[4]=2 (2,3)
i=5: dp[5]=3 (2,3,7) ou (2,5,7)
i=6: dp[6]=4 (2,3,7,101)
i=7: dp[7]=4 (2,3,7,18)
Patience Sorting (O(n log n)):
Processa 10: tails = [10]
Processa 9: tails = [9] (substitui 10)
Processa 2: tails = [2] (substitui 9)
Processa 5: tails = [2, 5] (estende)
Processa 3: tails = [2, 3] (substitui 5)
Processa 7: tails = [2, 3, 7] (estende)
Processa 101: tails = [2, 3, 7, 101] (estende)
Processa 18: tails = [2, 3, 7, 18] (substitui 101)
LIS comprimento = 4
Implementação em Zig
const std = @import("std");
const Allocator = std.mem.Allocator;
/// LIS com DP — O(n²) tempo, O(n) espaço.
/// Retorna o comprimento e a subsequência reconstruída.
pub fn lisDP(
allocator: Allocator,
arr: []const i32,
) !struct { comprimento: usize, subsequencia: []i32 } {
const n = arr.len;
if (n == 0) return .{ .comprimento = 0, .subsequencia = &.{} };
const dp = try allocator.alloc(usize, n);
defer allocator.free(dp);
const prev = try allocator.alloc(i32, n);
defer allocator.free(prev);
@memset(dp, 1);
@memset(prev, -1);
var max_len: usize = 1;
var max_idx: usize = 0;
for (1..n) |i| {
for (0..i) |j| {
if (arr[j] < arr[i] and dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1;
prev[i] = @intCast(j);
}
}
if (dp[i] > max_len) {
max_len = dp[i];
max_idx = i;
}
}
// Reconstrói
var resultado = try allocator.alloc(i32, max_len);
var idx: usize = max_len;
var cur: i32 = @intCast(max_idx);
while (cur >= 0) {
idx -= 1;
resultado[idx] = arr[@intCast(cur)];
cur = prev[@intCast(cur)];
}
return .{ .comprimento = max_len, .subsequencia = resultado };
}
/// LIS com busca binária — O(n log n) tempo, O(n) espaço.
pub fn lisBinario(allocator: Allocator, arr: []const i32) !usize {
if (arr.len == 0) return 0;
var tails = std.ArrayList(i32).init(allocator);
defer tails.deinit();
for (arr) |x| {
// Busca binária: menor posição onde tails[pos] >= x
var lo: usize = 0;
var hi: usize = tails.items.len;
while (lo < hi) {
const mid = lo + (hi - lo) / 2;
if (tails.items[mid] < x) {
lo = mid + 1;
} else {
hi = mid;
}
}
if (lo == tails.items.len) {
try tails.append(x);
} else {
tails.items[lo] = x;
}
}
return tails.items.len;
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const arr = [_]i32{ 10, 9, 2, 5, 3, 7, 101, 18 };
try stdout.print("Array: ", .{});
for (arr) |x| try stdout.print("{d} ", .{x});
try stdout.print("\n", .{});
// Versão O(n²) com reconstrução
const resultado = try lisDP(allocator, &arr);
defer allocator.free(resultado.subsequencia);
try stdout.print("LIS comprimento: {d}\n", .{resultado.comprimento});
try stdout.print("LIS: ", .{});
for (resultado.subsequencia) |x| try stdout.print("{d} ", .{x});
try stdout.print("\n", .{});
// Versão O(n log n)
const comp = try lisBinario(allocator, &arr);
try stdout.print("LIS (busca binária): {d}\n", .{comp});
}
Análise de Complexidade
| Método | Tempo | Espaço |
|---|---|---|
| DP (O(n^2)) | O(n^2) | O(n) |
| Busca binária | O(n log n) | O(n) |
Aplicações
- Controle de tráfego aéreo: maximizar sequências ordenadas de voos
- Análise de dados: encontrar tendências crescentes
- Bioinformática: alinhar sequências genéticas
- Teoria dos jogos: problemas de paciência (patience sorting)
Recursos Relacionados
- LCS — Subsequência Comum — Problema relacionado (LIS pode ser reduzido a LCS)
- Busca Binária — Usada na versão O(n log n)
- Knapsack (Mochila) — Outro problema clássico de DP
- Fibonacci (DP) — Introdução à programação dinâmica