El problema fundamental consiste en analizar un conjunto de coordenadas bidimensionales y determinar cuál línea recta contiene el mayor número de nodos dados. En el plano cartesiano X-Y, dos puntos siempre forman una línea; sin embargo, cuando se introduce un tercer punto o más, es necesario validar si pertenecen a la misma trayectoria geométrica.
Fundamento Matemático
La propiedad clave para resolver este desafío es la inclinación o pendiente. Dos pares de puntos comparten la misma recta si la relación entre sus diferencias verticales y horizontales es constante. Matemáticamente, para un punto de referencia $(x_i, y_i)$ y otro objetivo $(x_j, y_j)$, la pendiente $m$ se define como:
$m = \frac{y_j - y_i}{x_j - x_i}$
Si múltiples puntos generan el mismo valor de $m$ respecto a un origen fijo, están alineados. Existen consideraciones especiales para líneas verticales, donde el denominador es cero.
Enfoque basado en Mapas de Dispersión
Una estrategia efectiva implica iterar sobre cada punto del conjunto, tratándolo temporalmente como un ancla o vértice de una estrella. Desde esta posición fija, calculamos la inclinación hacia todos los demás puntos disponibles y agrupamos las cuentas por tipo de pendiente utilizando un objeto de almacenamiento asociativo (Hash Map).
A continuación, se presenta una implementación en Java que utiliza tipos de datos flotantes para representar la pendiente. Es importante manejar explícitamente los casos donde la diferencia horizontal es nula.
public class AnalizadorGeometrico {
public int calcularAlineacionMaxima(int[][] coordenadas) {
int totalNodos = coordenadas.length;
if (totalNodos < 3) {
return totalNodos;
}
int conteoGlobal = 0;
// Recorrer cada nodo considerando su rol de origen
for (int indiceOrigen = 0; indiceOrigen < totalNodos; indiceOrigen++) {
Map<double integer=""> diccionarioPendientes = new HashMap<>();
int coincidencias = 0;
int baseY = coordenadas[indiceOrigen][1];
int baseX = coordenadas[indiceOrigen][0];
for (int indiceDestino = 0; indiceDestino < totalNodos; indiceDestino++) {
if (indiceOrigen == indiceDestino) continue;
int destinoY = coordenadas[indiceDestino][1];
int destinoX = coordenadas[indiceDestino][0];
// Verificar superposición exacta
if (destinoY == baseY && destinoX == baseX) {
coincidencias++;
continue;
}
double angulo;
if (destinoX == baseX) {
// Caso vertical: infinito positivo
angulo = Double.POSITIVE_INFINITY;
} else {
angulo = (double) (destinoY - baseY) / (destinoX - baseX);
}
diccionarioPendientes.put(angulo, diccionarioPendientes.getOrDefault(angulo, 1) + 1);
}
// Evaluar el máximo local encontrado desde este origen
int maxLocal = coincidencias; // Incluir puntos duplicados
for (int frecuencia : diccionarioPendientes.values()) {
maxLocal = Math.max(maxLocal, frecuencia);
}
conteoGlobal = Math.max(conteoGlobal, maxLocal + coincidences); // Ajuste lógico según implementación real
}
return conteoGlobal;
}
}
</double>
Precisión Numérica mediante Fracciones Reducidas
El uso de números decimales puede introducir errores de redondeo al comparar pendientes muy cercanas pero distintas. Para mitigar esto, se opta por representar la inclinación como una fracción irreducible. Esto requiere calcular el Máximo Común Divisor (MCD) entre las diferencias de coordenadas $\Delta x$ y $\Delta y$, almacenando el resultado como una cadena de texto única (por ejemplo, "3/4") en lugar de un valor numérico directo.
Esta variante mejora la fiabilidad del algoritmo manteniendo la complejidad computacional cuadrática.
class SolucionPrecisa {
public int obtenerLargoMayor(int[][] data) {
if (data.length <= 2) return data.length;
int mejorResultado = 1;
for (int i = 0; i < data.length; i++) {
Map<string integer=""> contadores = new HashMap<>();
int mismoPunto = 1; // Contamos el punto actual inicialmente
for (int j = i + 1; j < data.length; j++) {
int deltaX = data[j][0] - data[i][0];
int deltaY = data[j][1] - data[i][1];
if (deltaX == 0 && deltaY == 0) {
mismoPunto++;
} else {
// Normalizar dirección usando MCD
int factor = obtenerMCD(deltaX, deltaY);
String clave = (deltaX / factor) + ":" + (deltaY / factor);
contadores.put(clave, contadores.getOrDefault(clave, 1) + 1);
}
}
int maxActual = mismoPunto;
for (int valor : contadores.values()) {
maxActual = Math.max(maxActual, valor + mismoPunto - 1);
}
mejorResultado = Math.max(mejorResultado, maxActual);
}
return mejorResultado;
}
// Función auxiliar recursiva para MCD
private int obtenerMCD(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
</string>
Optimización de Iteración
Es posible reducir operaciones redundantes restringiendo el segundo bucle para que comience solo desde el índice siguiente al actual ($j > i$). Dado que la relación de alineación es simétrica (si A está en la línea de B, B está en la línea de A), no es estrictamente necesario recalcularlo desde ambos sentidos dentro de la misma ejecución completa, aunque la lógica interna debe asegurarse de contar correctamente todas las contribuciones acumulativas. El siguiente código integra esta restricción junto con la normalización de fracciones para evitar cálculos repetidos.
public class OptimizadoLineal {
public int maxPuntosColineales(int[][] vertices) {
int n = vertices.length;
if (n < 3) return n;
int absolutoMax = 0;
for (int p1 = 0; p1 < n; p1++) {
HashMap<string integer=""> mapaRelaciones = new HashMap<>();
int duplicados = 0;
int maxIteracion = 0;
for (int p2 = p1 + 1; p2 < n; p2++) {
int dx = vertices[p1][0] - vertices[p2][0];
int dy = vertices[p1][1] - vertices[p2][1];
if (dx == 0 && dy == 0) {
duplicados++;
continue;
}
// Simplificar la pendiente para usarla como clave
int mcdVal = calcularMCD(dx, dy);
String clave = (dx / mcdVal) + "_" + (dy / mcdVal);
int cte = mapaRelaciones.getOrDefault(clave, 0) + 1;
mapaRelaciones.put(clave, cte);
maxIteracion = Math.max(maxIteracion, cte);
}
// Sumar el punto inicial (p1) + duplicados encontrados + mejores conexiones
absolutoMax = Math.max(absolutoMax, maxIteracion + duplicados + 1);
}
return absolutoMax;
}
private int calcularMCD(int a, int b) {
return (b == 0) ? a : calcularMCD(b, a % b);
}
}
</string>