LeetCode 3257 - Maximum Value Sum by Placing Three Rooks II
We are given an m x n matrix called board, where each cell contains an integer value. We must place exactly three rooks on the board. A rook attacks every cell in the same row and the same column.
Difficulty: 🔴 Hard
Topics: Array, Dynamic Programming, Matrix, Enumeration
Solution
Problem Understanding
We are given an m x n matrix called board, where each cell contains an integer value. We must place exactly three rooks on the board.
A rook attacks every cell in the same row and the same column. Therefore, for three rooks to be valid, no two rooks can share either a row or a column.
The goal is to maximize the sum of the values of the three chosen cells.
In other words, we want to select three positions:
(r1, c1)(r2, c2)(r3, c3)
such that:
r1,r2, andr3are all differentc1,c2, andc3are all different
and the sum:
$$board[r1][c1] + board[r2][c2] + board[r3][c3]$$
is as large as possible.
The constraints are important:
m, n <= 500- Values may be negative
- We must place exactly three rooks
A 500 x 500 board contains 250,000 cells, so any solution that tries all triples of cells is completely infeasible.
The presence of negative values is also important. We cannot greedily take only positive cells because sometimes every valid placement may include negative numbers. Since we must place exactly three rooks, the algorithm must always return some valid triple even if the total is negative.
Another subtle issue is that choosing the three individually largest cells does not necessarily work because they may conflict in rows or columns.
Approaches
Brute Force
The most direct approach is to try every possible combination of three cells.
For each triple of cells, we would check:
- whether all rows are distinct
- whether all columns are distinct
If valid, compute the sum and update the answer.
This works because it explicitly examines every legal placement.
However, the board can contain up to:
$$500 \times 500 = 250000$$
cells.
Trying all triples would require:
$$O((mn)^3)$$
operations, which is astronomically too large.
Even a heavily optimized brute-force solution cannot handle this input size.
Key Insight
We only need three rooks.
That changes the problem dramatically.
For any row, only a very small number of columns are actually relevant. If a column is not among the largest values in that row, it is extremely unlikely to participate in the optimal answer.
The crucial observation is:
For a solution using only three rows and three columns, we only need to consider the top few candidates from each row.
Specifically:
- For each row, keep only its top 3 values and corresponding columns.
- Then enumerate combinations of three rows.
- For those rows, try all combinations of their candidate columns.
- Keep only combinations with distinct columns.
Why does this work?
Because each row contributes only one rook. If a row uses a column outside its top 3 choices, then at least three better choices existed in that row. Since only two columns can be blocked by the other rooks, at least one better column would still remain available. Therefore an optimal solution never needs a column outside the top 3 for a row.
This reduces the search space enormously.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((mn)^3) | O(1) | Tries every triple of cells |
| Optimal | O(mn + m^3) | O(m) | Keeps only top 3 cells per row |
Algorithm Walkthrough
Step 1: Compute the top 3 cells for every row
For each row:
- scan all columns
- keep the three largest
(value, column)pairs
We only need three because:
- one column may conflict with rook #2
- one column may conflict with rook #3
- at least one top choice will still remain
This dramatically reduces the candidate space.
For each row we store:
[(value1, col1), (value2, col2), (value3, col3)]
sorted from largest to smallest value.
Step 2: Enumerate all triples of rows
Since rooks cannot share rows, we choose three distinct rows.
We iterate through:
r1 < r2 < r3
There are at most:
$$\binom{500}{3} \approx 20.7 \text{ million}$$
triples.
That sounds large, but the work per triple is tiny and bounded.
Step 3: Try all candidate column combinations
Each row has at most 3 candidate columns.
Therefore:
- row 1 contributes at most 3 choices
- row 2 contributes at most 3 choices
- row 3 contributes at most 3 choices
Total combinations per row triple:
$$3 \times 3 \times 3 = 27$$
For every combination:
- check whether the columns are distinct
- if valid, compute the sum
- update the answer
Step 4: Return the maximum sum
Because we examine every valid combination among the necessary candidate cells, the maximum encountered sum is the answer.
Why it works
For any row in an optimal solution, suppose the chosen column is not among the row's top 3 values.
Then there exist at least three strictly better columns in that row.
At most two columns are forbidden by the other two rooks. Therefore at least one better column remains available.
Replacing the chosen column with that better available column would improve the solution, contradicting optimality.
Thus every rook in an optimal solution must come from the top 3 values of its row.
Since the algorithm exhaustively checks all such possibilities, it must find the optimal answer.
Python Solution
from typing import List
class Solution:
def maximumValueSum(self, board: List[List[int]]) -> int:
m = len(board)
n = len(board[0])
# top3[row] = list of (value, col)
top3 = []
for row in board:
candidates = []
for col, value in enumerate(row):
candidates.append((value, col))
candidates.sort(reverse=True)
top3.append(candidates[:3])
answer = -10**18
for r1 in range(m):
for r2 in range(r1 + 1, m):
for r3 in range(r2 + 1, m):
for v1, c1 in top3[r1]:
for v2, c2 in top3[r2]:
if c1 == c2:
continue
for v3, c3 in top3[r3]:
if c1 == c3 or c2 == c3:
continue
answer = max(answer, v1 + v2 + v3)
return answer
The implementation starts by preprocessing each row independently.
For every row, we collect all (value, column) pairs and sort them in descending order. We then keep only the top three entries.
The nested loops over r1, r2, and r3 enumerate all valid row selections because rooks cannot share rows.
For each row triple, the algorithm tries every combination among the top three candidates of those rows. Since there are only 27 combinations, this portion is very small.
The column checks ensure that no two rooks attack each other. If all three columns are distinct, the sum is computed and used to update the global maximum.
The answer is initialized to a very negative number because board values may all be negative.
Go Solution
package main
import "sort"
func maximumValueSum(board [][]int) int64 {
m := len(board)
n := len(board[0])
type Pair struct {
value int
col int
}
top3 := make([][]Pair, m)
for r := 0; r < m; r++ {
candidates := make([]Pair, 0, n)
for c := 0; c < n; c++ {
candidates = append(candidates, Pair{
value: board[r][c],
col: c,
})
}
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].value > candidates[j].value
})
top3[r] = candidates[:3]
}
var answer int64 = -1 << 60
for r1 := 0; r1 < m; r1++ {
for r2 := r1 + 1; r2 < m; r2++ {
for r3 := r2 + 1; r3 < m; r3++ {
for _, p1 := range top3[r1] {
for _, p2 := range top3[r2] {
if p1.col == p2.col {
continue
}
for _, p3 := range top3[r3] {
if p1.col == p3.col || p2.col == p3.col {
continue
}
sum := int64(p1.value + p2.value + p3.value)
if sum > answer {
answer = sum
}
}
}
}
}
}
}
return answer
}
The Go implementation follows the same logic as the Python solution.
One important difference is integer handling. Since board values can be as large as 1e9, the total sum can reach 3e9, which exceeds 32-bit signed integer range. Therefore the final answer uses int64.
Slices are used to store the top three candidates per row. The sort.Slice function sorts each row's candidate list in descending order.
Worked Examples
Example 1
board =
[
[-3, 1, 1, 1],
[-3, 1, -3, 1],
[-3, 2, 1, 1]
]
Step 1: Top 3 values per row
| Row | Top 3 Candidates |
|---|---|
| 0 | (1,1) (1,2) (1,3) |
| 1 | (1,1) (1,3) (-3,0) |
| 2 | (2,1) (1,2) (1,3) |
Step 2: Only one row triple exists
(0,1,2)
Step 3: Try candidate combinations
| Row 0 | Row 1 | Row 2 | Valid? | Sum |
|---|---|---|---|---|
(1,1) |
(1,1) |
(2,1) |
No | - |
(1,1) |
(1,3) |
(1,2) |
Yes | 3 |
(1,2) |
(1,3) |
(2,1) |
Yes | 4 |
Maximum becomes 4.
Final answer:
4
Example 2
board =
[
[1,2,3],
[4,5,6],
[7,8,9]
]
Top candidates
| Row | Candidates |
|---|---|
| 0 | (3,2) (2,1) (1,0) |
| 1 | (6,2) (5,1) (4,0) |
| 2 | (9,2) (8,1) (7,0) |
Trying valid combinations:
| Columns | Sum |
|---|---|
(2,1,0) |
3+5+7=15 |
(1,0,2) |
2+4+9=15 |
Maximum:
15
Example 3
board =
[
[1,1,1],
[1,1,1],
[1,1,1]
]
Any permutation of three distinct columns works.
One valid placement:
| Position | Value |
|---|---|
(0,2) |
1 |
(1,1) |
1 |
(2,0) |
1 |
Total:
3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(mn + m^3) | Preprocessing rows plus enumerating row triples |
| Space | O(m) | Stores only top 3 candidates per row |
The preprocessing step scans every cell once, costing O(mn).
The dominant portion is enumerating triples of rows. There are:
$$O(m^3)$$
such triples.
For each triple we perform at most 27 constant-time checks, so the inner work is constant.
The memory usage is small because we store only three candidate cells per row.
Test Cases
from typing import List
class Solution:
def maximumValueSum(self, board: List[List[int]]) -> int:
m = len(board)
n = len(board[0]
top3 = []
for row in board:
candidates = []
for col, value in enumerate(row):
candidates.append((value, col))
candidates.sort(reverse=True)
top3.append(candidates[:3])
answer = -10**18
for r1 in range(m):
for r2 in range(r1 + 1, m):
for r3 in range(r2 + 1, m):
for v1, c1 in top3[r1]:
for v2, c2 in top3[r2]:
if c1 == c2:
continue
for v3, c3 in top3[r3]:
if c1 == c3 or c2 == c3:
continue
answer = max(answer, v1 + v2 + v3)
return answer
sol = Solution()
assert sol.maximumValueSum([
[-3,1,1,1],
[-3,1,-3,1],
[-3,2,1,1]
]) == 4 # provided example with negatives
assert sol.maximumValueSum([
[1,2,3],
[4,5,6],
[7,8,9]
]) == 15 # increasing values
assert sol.maximumValueSum([
[1,1,1],
[1,1,1],
[1,1,1]
]) == 3 # all equal values
assert sol.maximumValueSum([
[-1,-2,-3],
[-4,-5,-6],
[-7,-8,-9]
]) == -15 # all negative values
assert sol.maximumValueSum([
[100,1,1],
[1,100,1],
[1,1,100]
]) == 300 # diagonal optimal
assert sol.maximumValueSum([
[10,9,8],
[10,1,1],
[10,1,1]
]) == 20 # must avoid same column
assert sol.maximumValueSum([
[5,4,3,2],
[9,8,7,6],
[1,2,3,4]
]) == 17 # mixed choices
assert sol.maximumValueSum([
[10**9,1,1],
[1,10**9,1],
[1,1,10**9]
]) == 3 * 10**9 # large values
Test Summary
| Test | Why |
|---|---|
| Mixed positive and negative values | Verifies valid placement selection |
| Strictly increasing matrix | Tests obvious diagonal optimum |
| All equal values | Confirms any valid placement works |
| All negative values | Ensures algorithm still places exactly 3 rooks |
| Large diagonal values | Tests maximum selection |
| Shared best column | Ensures conflict handling works |
| Mixed rectangular board | Validates general correctness |
| Very large values | Tests integer overflow handling |
Edge Cases
All values are negative
This is a common source of bugs because some implementations incorrectly assume that skipping placements is allowed. The problem requires placing exactly three rooks, even if the total becomes negative.
The implementation handles this correctly by initializing the answer to a very small number and always evaluating valid triples.
Multiple rows prefer the same column
Several rows may have their largest value in the same column. A greedy algorithm that independently chooses the maximum value from each row would fail.
The implementation explicitly checks column conflicts before accepting a combination, ensuring that all three columns are distinct.
Many duplicate values
When rows contain repeated values, there may be many equally optimal solutions. Some algorithms accidentally discard duplicates incorrectly during preprocessing.
This implementation keeps (value, column) pairs, not just values, so duplicate values in different columns remain distinguishable and valid placements are preserved.