Start learning free

LESSON 221 OF 338 · PARTIAL SUMS #2

Max Sum Square in Go: Brute Force over Every Subsquare

Practice O(n2 * m) Homework Read the full explanation

Given a n x m grid filled with integers, find the subsquare with maximum sum among all submatrices.

Example:

Input:
[
  [ 1,  -9, -10,  1],
  [-1,  10,  10,  1],
  [ 0,   9,   9, -9],
  [-1,   3,  -1, -1]
]
Output: 38
Explanation: 2x2 Submatrix [[10, 10], [9, 9]]

Note: A subsquare is a submatrix having the same number of columns and rows

SOLVE IT YOURSELF
Ctrl + Enter to check

GO · PARTIAL SUMS #2

Max Sum Square in Go, explained

Problem overview

The method maxSum receives a grid of integers, grid, with n rows and m columns, and returns the largest sum of any subsquare: a block of cells with the same number of rows and columns. Values can be negative, so a bigger square is not always a better one.

In the example grid

 1  -9 -10   1
-1  10  10   1
 0   9   9  -9
-1   3  -1  -1

the best subsquare is the 2x2 block [[10, 10], [9, 9]], whose sum is 38. A single cell is also a subsquare (1x1), which matters when every value is negative.

Approach: try every square, add up its cells

The simplest correct idea is to describe a square by two corners, the top-left cell (x1, y1) and the bottom-right cell (x2, y2), and try all pairs. A block is a square exactly when it spans the same number of rows and columns, that is, when x2 - x1 == y2 - y1. For each such pair, add up the cells in the block and keep the largest total.

This is a brute-force solution. It is easy to get right, and it is the baseline that the chapter's partial-sum technique improves: with prefix sums, the sum of any block is available in constant time instead of being recomputed cell by cell.

Step by step

  1. Write a helper that sums a block. rectSum takes the grid and both corners, and adds every grid[x][y] with x from x1 to x2 and y from y1 to y2, both ends included.
  2. Read the dimensions. n is the number of rows, len(grid), and m is the number of columns, len(grid[0]).
  3. Start result with a real answer. Setting it to grid[0][0], the sum of a valid 1x1 square, is safer than starting at 0. If every cell is negative, the answer is negative, and a starting value of 0 would wrongly beat it.
  4. Choose the top-left corner with two loops over x1 and y1.
  5. Choose the bottom-right corner with two more loops, starting at x1 and y1 so that the corner is never above or to the left of the first one.
  6. Keep only squares. Skip every pair where the height x2 - x1 differs from the width y2 - y1.
  7. Update the best sum with the larger of the current result and the block's sum.
  8. Return result after all the loops.

The solution in Go

type Solution struct {}

func (this Solution) rectSum(grid [][]int, x1, y1, x2, y2 int) int {
    sum := 0
    for x := x1; x <= x2; x++ {
        for y := y1; y <= y2; y++ {
            sum += grid[x][y]
        }
    }
    return sum
}

func (this Solution) maxSum(grid [][]int) int {
    n := len(grid)
    m := len(grid[0])
    result := grid[0][0]
    for x1 := 0; x1 < n; x1++ {
        for y1 := 0; y1 < m; y1++ {
            for x2 := x1; x2 < n; x2++ {
                for y2 := y1; y2 < m; y2++ {
                    if x2-x1 == y2-y1 {
                        result = max(result, this.rectSum(grid, x1, y1, x2, y2))
                    }
                }
            }
        }
    }
    return result
}

The Go details worth a closer look:

  • Methods on a struct. Go has no classes. Solution is an empty struct, and func (this Solution) maxSum(...) attaches a method to it. The part in parentheses is the receiver; here it is named this, and the code calls the other method as this.rectSum(...). Go programmers usually pick a short name from the type, such as s, but any name works.
  • Grids are slices of slices. [][]int is a slice whose elements are slices, one per row. len(grid) counts rows and len(grid[0]) counts the columns of the first row, so the code assumes the grid isn't empty. Rows can in principle have different lengths; the problem gives a rectangular grid.
  • The built-in max. Since Go 1.21, max is part of the language and accepts any ordered values, so no helper function or math import is needed. Older Go versions don't have it.
  • No parentheses, one loop keyword. Every loop here is a three-part for. The condition has no parentheses, and the braces are required.
  • Short declarations. := declares and initialises a variable at once, and Go infers the type, so sum and result are int. An unused variable is a compile error, which is why every name in the code is used.

Complexity

Let the grid be n x m, and let k be the smaller of the two.

Time. The four nested loops visit every pair of corners, about n² * m² pairs in the worst case, and the if keeps the ones that form squares. Each kept square of side s costs s² additions in rectSum. Counting the squares of each side, the total is on the order of n * m * k³, which is O(n⁵) for a square grid. That is fine for small grids and too slow for large ones.

Space: O(1). Besides the input, the code keeps a handful of integers.

The wasted work is visible: neighbouring squares share most of their cells, but rectSum adds them again from scratch each time. A prefix-sum table removes that repetition.

Edge cases and common mistakes

  • All values negative. For [[-5, -2], [-3, -4]] the answer is -2, the best single cell. Starting result at 0 would return 0, which is not the sum of any subsquare.
  • A single cell. [[-5]] returns -5.
  • Non-square grids. For a 2x3 grid, squares are at most 2x2. The loops handle this without extra code, because the end corner can never leave the grid.
  • Exclusive end corners. The loops in rectSum use <=, because both corners belong to the block. Using < would drop the last row and column, and 1x1 squares would sum to 0.
  • Checking only the largest square. A bigger square can contain negative cells and lose to a smaller one. In [[7, -20, 7], [-20, -20, -20]] the answer is 7, from a single cell.

Testing

  • The example grid above gives 38.
  • [[-5]] gives -5.
  • [[-5, -2], [-3, -4]] gives -2.
  • [[1, 2, 3], [4, 5, 6]] gives 16, from the 2x2 block [[2, 3], [5, 6]].
  • [[7, -20, 7], [-20, -20, -20]] gives 7.
Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
Start learning free