From f3a5e039d95e35963f9493e9d99162f26e3e255e Mon Sep 17 00:00:00 2001 From: Adarsh singh Date: Sun, 19 Oct 2025 15:14:38 +0530 Subject: [PATCH] feat: Add Kadane's 2D algorithm for maximum sum rectangle --- .../com/thealgorithms/matrix/Kadane2D.java | 156 ++++++++++++++++++ .../thealgorithms/matrix/Kadane2DTest.java | 119 +++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 src/main/java/com/thealgorithms/matrix/Kadane2D.java create mode 100644 src/test/java/com/thealgorithms/matrix/Kadane2DTest.java diff --git a/src/main/java/com/thealgorithms/matrix/Kadane2D.java b/src/main/java/com/thealgorithms/matrix/Kadane2D.java new file mode 100644 index 000000000000..b4591bff76b9 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/Kadane2D.java @@ -0,0 +1,156 @@ +package com.thealgorithms.matrix; + + +/** + * Kadane's Algorithm for 2D arrays (Maximum Sum Rectangle in a 2D Matrix) + * + * This algorithm finds the maximum sum of elements in a rectangular submatrix + * of a given 2D matrix. It uses Kadane's algorithm for 1D arrays extended to 2D. + * + * Time Complexity: O(n^2 * m) where n is the number of columns and m is the number of rows + * Space Complexity: O(m) for the temporary array + * + * @author Adarsh Singh + */ +public final class Kadane2D { + + + + /** + * Finds the maximum sum rectangle in a 2D matrix + * + * @param matrix The input 2D matrix + * @return The maximum sum found in any rectangular submatrix + * @throws IllegalArgumentException if matrix is null or empty + */ + public static int maxSumRectangle(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + throw new IllegalArgumentException("Matrix cannot be null or empty"); + } + + int rows = matrix.length; + int cols = matrix[0].length; + int maxSum = Integer.MIN_VALUE; + + // Fix the left column + for (int left = 0; left < cols; left++) { + int[] temp = new int[rows]; + + // Calculate sum between left and right columns + for (int right = left; right < cols; right++) { + // Add elements of current column to temp array + for (int i = 0; i < rows; i++) { + temp[i] += matrix[i][right]; + } + + // Apply Kadane's 1D algorithm on temp array + int currentMax = kadane1D(temp); + maxSum = Math.max(maxSum, currentMax); + } + } + + return maxSum; + } + + /** + * Kadane's algorithm for 1D array + * Finds maximum sum of contiguous subarray + * + * @param arr The input array + * @return Maximum sum of contiguous subarray + */ + private static int kadane1D(int[] arr) { + int maxSoFar = arr[0]; + int maxEndingHere = arr[0]; + + for (int i = 1; i < arr.length; i++) { + maxEndingHere = Math.max(arr[i], maxEndingHere + arr[i]); + maxSoFar = Math.max(maxSoFar, maxEndingHere); + } + + return maxSoFar; + } + + /** + * Result class to store coordinates along with maximum sum + */ + public static class Result { + public final int maxSum; + public final int topRow; + public final int leftCol; + public final int bottomRow; + public final int rightCol; + + public Result(int maxSum, int topRow, int leftCol, int bottomRow, int rightCol) { + this.maxSum = maxSum; + this.topRow = topRow; + this.leftCol = leftCol; + this.bottomRow = bottomRow; + this.rightCol = rightCol; + } + + @Override + public String toString() { + return String.format("Max Sum: %d, Top-Left: (%d, %d), Bottom-Right: (%d, %d)", + maxSum, topRow, leftCol, bottomRow, rightCol); + } + } + + /** + * Finds the maximum sum rectangle along with its coordinates + * + * @param matrix The input 2D matrix + * @return Result object containing maximum sum and rectangle coordinates + * @throws IllegalArgumentException if matrix is null or empty + */ + public static Result maxSumRectangleWithCoordinates(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + throw new IllegalArgumentException("Matrix cannot be null or empty"); + } + + int rows = matrix.length; + int cols = matrix[0].length; + int maxSum = Integer.MIN_VALUE; + int finalLeft = 0, finalRight = 0, finalTop = 0, finalBottom = 0; + + for (int left = 0; left < cols; left++) { + int[] temp = new int[rows]; + + for (int right = left; right < cols; right++) { + for (int i = 0; i < rows; i++) { + temp[i] += matrix[i][right]; + } + + // Modified Kadane's to track row positions + int currentSum = temp[0]; + int maxTemp = temp[0]; + int start = 0, end = 0, s = 0; + + for (int i = 1; i < rows; i++) { + if (currentSum < 0) { + currentSum = temp[i]; + s = i; + } else { + currentSum += temp[i]; + } + + if (currentSum > maxTemp) { + maxTemp = currentSum; + start = s; + end = i; + } + } + + if (maxTemp > maxSum) { + maxSum = maxTemp; + finalLeft = left; + finalRight = right; + finalTop = start; + finalBottom = end; + } + } + } + + return new Result(maxSum, finalTop, finalLeft, finalBottom, finalRight); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/Kadane2DTest.java b/src/test/java/com/thealgorithms/matrix/Kadane2DTest.java new file mode 100644 index 000000000000..5b2a3b552b7e --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/Kadane2DTest.java @@ -0,0 +1,119 @@ +package com.thealgorithms.matrix; + + + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test cases for Kadane2D algorithm + */ +class Kadane2DTest { + + @Test + void testBasicMatrix() { + int[][] matrix = { + {1, 2, -1, -4, -20}, + {-8, -3, 4, 2, 1}, + {3, 8, 10, 1, 3}, + {-4, -1, 1, 7, -6} + }; + + assertEquals(29, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testAllPositive() { + int[][] matrix = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9} + }; + + // Sum of all elements + assertEquals(45, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testAllNegative() { + int[][] matrix = { + {-1, -2, -3}, + {-4, -5, -6}, + {-7, -8, -9} + }; + + // Maximum single element (least negative) + assertEquals(-1, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testSingleElement() { + int[][] matrix = {{5}}; + assertEquals(5, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testSingleRow() { + int[][] matrix = {{-2, 1, -3, 4, -1, 2, 1, -5, 4}}; + assertEquals(6, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testSingleColumn() { + int[][] matrix = {{-2}, {1}, {-3}, {4}, {-1}, {2}, {1}, {-5}, {4}}; + assertEquals(6, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testMixedValues() { + int[][] matrix = { + {2, 1, -3, -4}, + {-5, -1, 4, 0}, + {1, 2, 3, -2} + }; + + assertEquals(9, Kadane2D.maxSumRectangle(matrix)); + } + + @Test + void testWithCoordinates() { + int[][] matrix = { + {1, 2, -1, -4, -20}, + {-8, -3, 4, 2, 1}, + {3, 8, 10, 1, 3}, + {-4, -1, 1, 7, -6} + }; + + Kadane2D.Result result = Kadane2D.maxSumRectangleWithCoordinates(matrix); + assertEquals(29, result.maxSum); + assertEquals(1, result.topRow); + assertEquals(1, result.leftCol); + assertEquals(2, result.bottomRow); + assertEquals(3, result.rightCol); + } + + @Test + void testNullMatrix() { + assertThrows(IllegalArgumentException.class, () -> { + Kadane2D.maxSumRectangle(null); + }); + } + + @Test + void testEmptyMatrix() { + assertThrows(IllegalArgumentException.class, () -> { + Kadane2D.maxSumRectangle(new int[0][0]); + }); + } + + @Test + void testZeroMatrix() { + int[][] matrix = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0} + }; + + assertEquals(0, Kadane2D.maxSumRectangle(matrix)); + } +}