Полное условие задачи звучит так: дана матрица размером N*N, содержащая положительные и отрицательные числа. Напишите код поиска субматрицы с максимально возможной суммой.
Существует множество решений этой задачи. Мы начнем с метода грубой силы, а затем займемся оптимизацией.
Метод грубой силы: O(N⁶)
Подобно другим задачам, связанным с поиском максимума, у этой задачи есть простое решение. Достаточно проверить все субматрицы, вычислить сумму каждой и найти самую большую.
Чтобы проверить все субматрицы и избежать повторов. Придется пройтись по всем упорядоченным парам строк и затем по всем упорядоченным парам столбцов.
Это решение потребует O(N⁶) времени, так как необходимо проверить O(N⁴) матриц, а проверка одной матрицы занимает O(N²) времени.
Динамическое программирование: O(N⁴)
Обратите внимание, что предыдущее решение работает медленно из-за расчета суммы элементов матрицы — O(N²) — очень медленная операция. Можно ли сократить это время? Да! Мы можем уменьшить время computeSum до O(1).
Можно заранее рассчитать подобные значения и затем найти максимальную субматрицу. Следующий код реализует данный алгоритм.
int getMaxMatrix(int[][] original) {
int maxArea = Integer.MIN_VALUE; // Важно! Max может быть < 0
int rowCount = original.length;
int columnCount = original[0].length;
int[][] matrix = precomputeMatrix(original);
for (int rowl = 0; rowl < rowCount; rowl++) {
for (int row2 = rowl; row2 < rowCount; row2++) {
for (int coli = 0; coli < columnCount; coll++) {
for (int col2 = coli; col2 < columnCount; col2++) {
maxArea = Math.max(maxArea, computeSum(matrix, rowl, row2, coli, col2));
}
}
}
}
return maxArea
}
int[][] precomputeMatrix(int[][] matrix) {
int[][] sumMatrix = new int[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (i == 0 && j == 0) { // первая ячейка
sumMatrix[i][j] = matrix[i][j];
} else if (j == 0) { // ячейка в первой колонке
sumMatrix[i][j] = sumMatrix[i - l][j] + matrix[i][j];
} else if (i == 0) { // ячейка в первом ряду
sumMatrix[i][j] = sumMatrix[i][j-1] + matrix[i][j];
} else {
sumMatrix[i][j] = sumMatrix[i-l][j] +
sumMatrix[i][j-1] - sumMatrix[i-l][j-1] +
matrix[i][j];
}
}
}
return sumMatrix;
}
int computeSum(int[][] sumMatrix, int il, int i2, int jl, int j2) {
if (il == 0 && jl == 0) { // начинаем с ряда 0, колонки 0
return sumMatrix[i2][j2];
} else if (il == 0) { // начинаем с ряда 0
return sumMatrix[i2][j2] - sumMatrix[i2][jl - 1];
} else if (jl == 0) { // начинаем с колонки 0
return sumMatrix[i2][j2] - sumMatrix[il-l][j2];
} else {
return sumMatrix[i2][j2] - sumMatrix[i2][jl-1] - sumMatrix[il-l][j2] + sumMatrix[il-1][jl-1];
}
}
Оптимизированное решение: O(N³)
Невероятно, но существует еще более оптимальное решение. Если у нас есть R строк и С столбцов, то задачу можно решить за О(R²C) времени.
Вспомните решение задачи про поиск максимального субмассива: для массива целых чисел (integer) найдите субмассив с максимальной суммой. Такой максимальный субмассив можно найти за О(N) времени. Давайте используем это решение для нашей задачи.
Каждую субматрицу можно представить в виде последовательности строк и последовательности столбцов. Можно пройтись по строкам и найти столбцы, дающие максимальную сумму.
Код будет таким:
maxSum = 0
foreach rowStart in rows
foreach rowEnd in rows
/* У нас есть количество возможных субматриц с границами
* rowStart и rowEnd
* Найдите границы colStart и colEnd, дающие
* максимальную сумму. */
maxSum = max(runningMaxSum, maxSum)
return maxSum
Теперь остается вопрос: как найти «лучшие» colStart и colEnd?
Рассмотрим субматрицу:
Нам необходимо найти colStart и colEnd, которые дают нам максимально возможную сумму всех субматриц rowStart сверху и rowEnd снизу. Можно вычислить сумму каждого столбца и использовать функцию maximumSubArray, которая обсуждалась в начале решения этой задачи.
В предыдущем примере максимальный субмасив охватывал пространство с первой по четвертую колонку. Это означает, что максимальная субматрица должна простираться от (rowStart, первый столбец) до (rowEnd, четвертый столбец).
Теперь мы можем записать следующий псевдокод:
maxSum = 0
foreach rowStart in rows
foreach rowEnd in rows
foreach col in columns
partialSum[col] = sum of matrix[rowStart, col] through
matrix[rowEnd, col]
runningMaxSum = maxSubArray(partialSum)
maxSum = max(runningMaxSum, maxSum)
return maxSum
Вычисление суммы в строках 5 и 6 занимает R*C времени (так как требует итерации от rowStart до rowEnd), что дает общее время выполнения О(R ³ C).
В строках 5 и 6 мы добавляли a[0]…a[i] с нуля, даже если на предыдущей итерации внешнего цикла добавились a[0]…a[i-1]. Давайте избавимся от двойной работы.
maxSum = 0
foreach rowStart in rows
clear array partialSum
foreach rowEnd in rows
foreach col in columns
partialSum[col] += matrix[rowEnd, col]
runningMaxSum = maxSubArray(partialSum)
maxSum = max(runningMaxSum, maxSum)
return maxSum
Полная версия кода выглядит так:
public void clearArray(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = 0;
}
}
public static int maxSubMatrix(int[][] matrix) {
int rowCount = matrix.length;
int colCount = matrix[0].length;
int[] partialSum = new int[colCount];
int maxSum = 0; // Макс, сумма = 0 (матрица пуста)
for (int rowStart = 0; rowStart < rowCount; rowStart++) {
clearArray(partialSum);
for (int rowEnd = rowStart; rowEnd < rowCount; rowEnd++) {
for (int i = 0; i < colCount; i++) {
partialSum[i] += matrix[rowEnd][i]j
}
int tempMaxSum = maxSubArray(partialSum, colCount);
/* Если вы хотите отслеживать координаты, добавьте
* код здесь, чтобы сделать это. */
maxSum = Math.max(maxSum, tempMaxSum);
}
}
return maxSum;
}
public static int maxSubArray(int array[], int N) {
int maxSum = 0;
int runningSum = 0;
for (int i = 0; i < N; i++) {
runningSum += array[i];
maxSum = Math.max(maxSum, runningSum);
/* Если runningSum < 0, нет смысла продолжать ряд
* Сброс. */
if (runningSum < 0) {
runningSum = 0;
}
}
return maxSum;
}
Это чрезвычайно сложная задача. Вряд ли вы сможете решить подобную задачу на собеседовании без подсказки интервьюера.
Разбор задачи по книге «Карьера программиста. Как устроиться на работу в Google, Microsoft или другую ведущую IT-компанию»