1981. Minimize the Difference Between Target and Chosen Elements
Problem Description
You have a matrix mat
with m
rows and n
columns containing integers, and you're given a target value target
.
Your task is to select exactly one integer from each row of the matrix. After selecting one number from every row, you calculate the sum of all selected numbers. The goal is to make this sum as close as possible to the target value.
Specifically, you need to minimize the absolute difference between your sum and the target. The absolute difference between two numbers a
and b
is calculated as |a - b|
.
For example, if you have a 3×3 matrix and need to pick one number from each row, you might pick the first element from row 1, the second element from row 2, and the third element from row 3. You would then sum these three numbers and find how far this sum is from your target.
The function should return the minimum possible absolute difference you can achieve by optimally selecting one element from each row.
Intuition
The key insight is that we need to explore all possible sums that can be formed by picking one element from each row. This is essentially a path-finding problem where each "path" represents a series of choices (one per row) and leads to a final sum.
We can think of this problem incrementally: after processing the first row, we know all possible sums we can achieve (which are just the elements in the first row). When we move to the second row, each previously achievable sum can be combined with each element in the second row to create new possible sums. This process continues row by row.
The approach uses dynamic programming with a set to track all achievable sums. Starting with f = {0}
(representing no sum initially), we iterate through each row. For each row, we generate all new possible sums by adding each element in the current row to each previously achievable sum. This is captured in the expression set(a + b for a in f for b in row)
.
Why does this work? Because we're essentially building up all possible combinations systematically. If we can achieve a sum s
using the first i
rows, and row i+1
contains element x
, then we can achieve sum s + x
using the first i+1
rows.
After processing all rows, the set f
contains all possible sums that can be achieved by selecting one element from each row. We then simply find which sum in this set has the minimum absolute difference from our target using min(abs(v - target) for v in f)
.
This approach is efficient because it avoids redundant calculations - if multiple paths lead to the same sum, we only store that sum once in our set.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution implements a dynamic programming approach, specifically a variant of the grouped knapsack problem. Here's how the implementation works:
Data Structure: We use a set f
to store all possible sums that can be achieved at each stage. Sets are ideal here because they automatically handle duplicates - if multiple combinations lead to the same sum, we only store it once.
Algorithm Steps:
-
Initialize: Start with
f = {0}
, representing that before selecting any elements, the sum is 0. -
Iterate through rows: For each row in the matrix:
- Generate all new possible sums by combining existing sums with elements from the current row
- Update
f
with these new sums using:f = set(a + b for a in f for b in row)
- This creates a Cartesian product between existing sums and current row elements
-
Find minimum difference: After processing all rows,
f
contains all achievable sums. Calculatemin(abs(v - target) for v in f)
to find the sum closest to the target.
Dynamic Programming State:
The state f[i][j]
in the reference approach represents whether sum j
is achievable using elements from the first i
rows. The implementation optimizes this by:
- Using a rolling array technique (keeping only the current state in set
f
) - Instead of a 2D boolean array, using a set to store only the achievable sums
Space Optimization:
Rather than maintaining a full 2D DP table f[i][j]
, the solution uses a single set that gets updated row by row. This reduces space complexity while maintaining the same logic - if sum j
was achievable with i-1
rows and we add element x
from row i
, then sum j+x
becomes achievable with i
rows.
Time Complexity: O(m × n × S) where m is the number of rows, n is the average number of elements per row, and S is the size of the set of possible sums.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example Input:
- Matrix
mat = [[1, 2], [3, 4], [5, 6]]
(3 rows × 2 columns) - Target
target = 10
Goal: Select one element from each row to get a sum as close as possible to 10.
Step-by-step execution:
Initial State:
f = {0}
(no elements selected yet, sum is 0)
Processing Row 1: [1, 2]
- For each existing sum in
f
(currently just 0), add each element from row 1 - New sums:
0 + 1 = 1
and0 + 2 = 2
- Update:
f = {1, 2}
- These represent: selecting 1 from row 1 OR selecting 2 from row 1
Processing Row 2: [3, 4]
- For each sum in
f = {1, 2}
, add each element from row 2:- From sum 1:
1 + 3 = 4
and1 + 4 = 5
- From sum 2:
2 + 3 = 5
and2 + 4 = 6
- From sum 1:
- Update:
f = {4, 5, 6}
- These represent all possible sums after selecting from rows 1 and 2
Processing Row 3: [5, 6]
- For each sum in
f = {4, 5, 6}
, add each element from row 3:- From sum 4:
4 + 5 = 9
and4 + 6 = 10
- From sum 5:
5 + 5 = 10
and5 + 6 = 11
- From sum 6:
6 + 5 = 11
and6 + 6 = 12
- From sum 4:
- Update:
f = {9, 10, 11, 12}
- These are all possible sums after selecting one element from each row
Finding the Answer:
- Calculate absolute differences from target (10):
|9 - 10| = 1
|10 - 10| = 0
← minimum!|11 - 10| = 1
|12 - 10| = 2
- Return:
0
(the minimum absolute difference)
Verification: The sum of 10 can be achieved by:
- Selecting 1 from row 1, 3 from row 2, and 6 from row 3:
1 + 3 + 6 = 10
- Or selecting 2 from row 1, 3 from row 2, and 5 from row 3:
2 + 3 + 5 = 10
Solution Implementation
1class Solution:
2 def minimizeTheDifference(self, mat: List[List[int]], target: int) -> int:
3 # Initialize a set to store all possible sums
4 # Starting with 0 as the base sum
5 possible_sums = {0}
6
7 # Iterate through each row in the matrix
8 for row in mat:
9 # Generate new possible sums by adding each element in current row
10 # to each existing sum from previous rows
11 new_sums = set()
12 for current_sum in possible_sums:
13 for element in row:
14 new_sums.add(current_sum + element)
15
16 # Update possible_sums with the new set of sums
17 possible_sums = new_sums
18
19 # Find the sum that minimizes the absolute difference with target
20 min_difference = min(abs(sum_value - target) for sum_value in possible_sums)
21
22 return min_difference
23
1class Solution {
2 public int minimizeTheDifference(int[][] mat, int target) {
3 // Initialize DP array where dp[i] = true means sum i is achievable
4 // Initially, only sum 0 is achievable (by selecting nothing)
5 boolean[] dp = {true};
6
7 // Process each row of the matrix
8 for (int[] currentRow : mat) {
9 // Find the maximum value in the current row
10 int maxValueInRow = 0;
11 for (int value : currentRow) {
12 maxValueInRow = Math.max(maxValueInRow, value);
13 }
14
15 // Create new DP array with expanded size to accommodate new possible sums
16 // Size is increased by maxValueInRow to handle the largest possible new sum
17 boolean[] nextDp = new boolean[dp.length + maxValueInRow];
18
19 // For each value in the current row
20 for (int value : currentRow) {
21 // Update all possible sums by adding current value
22 // Iterate through all positions where we can place this value
23 for (int sum = value; sum < dp.length + value; sum++) {
24 // If (sum - value) was achievable in previous step,
25 // then sum is achievable by adding current value
26 nextDp[sum] |= dp[sum - value];
27 }
28 }
29
30 // Move to the next iteration with updated DP array
31 dp = nextDp;
32 }
33
34 // Find the achievable sum closest to the target
35 int minDifference = 1 << 30; // Initialize with a large number (2^30)
36 for (int sum = 0; sum < dp.length; sum++) {
37 if (dp[sum]) {
38 // If this sum is achievable, calculate its difference from target
39 minDifference = Math.min(minDifference, Math.abs(sum - target));
40 }
41 }
42
43 return minDifference;
44 }
45}
46
1class Solution {
2public:
3 int minimizeTheDifference(vector<vector<int>>& mat, int target) {
4 // dp[i] = 1 if sum i is achievable, 0 otherwise
5 // Initially, only sum 0 is achievable (empty selection)
6 vector<int> dp = {1};
7
8 // Process each row of the matrix
9 for (auto& row : mat) {
10 // Find the maximum value in the current row
11 int maxValue = *max_element(row.begin(), row.end());
12
13 // Create new DP array for the next state
14 // Size is extended by maxValue to accommodate new possible sums
15 vector<int> nextDp(dp.size() + maxValue);
16
17 // For each element in the current row
18 for (int value : row) {
19 // Update all possible sums by adding current value
20 // j represents the new sum, j - value is the previous sum
21 for (int j = value; j < dp.size() + value; ++j) {
22 // If sum (j - value) was achievable, then sum j is achievable
23 nextDp[j] |= dp[j - value];
24 }
25 }
26
27 // Move to the next state
28 dp = move(nextDp);
29 }
30
31 // Find the achievable sum closest to target
32 int minDifference = 1 << 30; // Initialize with a large value
33 for (int sum = 0; sum < dp.size(); ++sum) {
34 // If this sum is achievable
35 if (dp[sum]) {
36 // Update minimum difference
37 minDifference = min(minDifference, abs(sum - target));
38 }
39 }
40
41 return minDifference;
42 }
43};
44
1function minimizeTheDifference(mat: number[][], target: number): number {
2 // dp[i] = 1 if sum i is achievable, 0 otherwise
3 // Initially, only sum 0 is achievable (empty selection)
4 let dp: number[] = [1];
5
6 // Process each row of the matrix
7 for (const row of mat) {
8 // Find the maximum value in the current row
9 const maxValue = Math.max(...row);
10
11 // Create new DP array for the next state
12 // Size is extended by maxValue to accommodate new possible sums
13 const nextDp: number[] = new Array(dp.length + maxValue).fill(0);
14
15 // For each element in the current row
16 for (const value of row) {
17 // Update all possible sums by adding current value
18 // j represents the new sum, j - value is the previous sum
19 for (let j = value; j < dp.length + value; j++) {
20 // If sum (j - value) was achievable, then sum j is achievable
21 nextDp[j] = nextDp[j] | dp[j - value];
22 }
23 }
24
25 // Move to the next state
26 dp = nextDp;
27 }
28
29 // Find the achievable sum closest to target
30 let minDifference = 1 << 30; // Initialize with a large value
31 for (let sum = 0; sum < dp.length; sum++) {
32 // If this sum is achievable
33 if (dp[sum]) {
34 // Update minimum difference
35 minDifference = Math.min(minDifference, Math.abs(sum - target));
36 }
37 }
38
39 return minDifference;
40}
41
Time and Space Complexity
The time complexity is O(m^2 × n × C)
where:
m
is the number of rows in the matrixn
is the number of columns in the matrixC
is the maximum value of elements in the matrix
This complexity arises because:
- We iterate through
m
rows - For each row, we generate a new set by combining each element in the current set
f
with each element in the current row - The size of set
f
can grow up toO(m × C)
in the worst case (since after processingk
rows, the maximum possible sum isk × C
) - For each row, we perform
|f| × n
operations, which isO(m × C × n)
in the worst case - Since we do this for
m
rows, the total complexity isO(m × m × C × n) = O(m^2 × n × C)
The space complexity is O(m × C)
because:
- The set
f
stores all possible sums - After processing all
m
rows, the maximum possible sum ism × C
- In the worst case, we might have
O(m × C)
distinct sums stored in the set - The temporary set created during each iteration also takes at most
O(m × C)
space
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Memory Limit Exceeded Due to Unbounded Set Growth
The Problem: The set of possible sums can grow exponentially. With m rows and n elements per row, theoretically we could have up to n^m different sums. For large matrices with diverse values, the set possible_sums
can consume excessive memory, leading to Memory Limit Exceeded (MLE) errors.
Example Scenario: Consider a matrix with 70 rows where each row contains [1, 2, 3, ..., 70]. The number of possible sums grows rapidly, potentially creating millions of unique values in the set.
Solution - Pruning Strategy: Since we're looking for sums close to the target, we can prune sums that are already far from the target and unlikely to produce better results:
class Solution:
def minimizeTheDifference(self, mat: List[List[int]], target: int) -> int:
possible_sums = {0}
for row in mat:
new_sums = set()
for current_sum in possible_sums:
for element in row:
new_sums.add(current_sum + element)
# Pruning: Keep only relevant sums
if len(new_sums) > 5000: # Threshold to prevent memory issues
# Sort sums and keep those closest to target
sorted_sums = sorted(new_sums, key=lambda x: abs(x - target))
new_sums = set(sorted_sums[:5000])
# Alternative pruning: Remove sums that are too far from target
# if we already have a sum equal to or very close to target
if target in new_sums:
# Keep sums within a reasonable range of target
new_sums = {s for s in new_sums if abs(s - target) <= 100}
possible_sums = new_sums
return min(abs(sum_value - target) for sum_value in possible_sums)
2. Early Termination Optimization Missing
The Problem: The original solution continues processing even after finding an exact match (sum equal to target), which is unnecessary since the minimum difference would be 0.
Solution - Early Exit:
class Solution:
def minimizeTheDifference(self, mat: List[List[int]], target: int) -> int:
possible_sums = {0}
for row in mat:
new_sums = set()
for current_sum in possible_sums:
for element in row:
new_sum = current_sum + element
new_sums.add(new_sum)
# Early termination if we hit the target exactly
if new_sum == target:
# Check if this is the last row
if row is mat[-1]:
return 0
possible_sums = new_sums
# Check if target is achievable after processing this row
if target in possible_sums:
# Calculate minimum remaining rows contribution
remaining_rows = len(mat) - mat.index(row) - 1
if remaining_rows == 0:
return 0
return min(abs(sum_value - target) for sum_value in possible_sums)
3. Optimization for Large Targets
The Problem: When the target is very large compared to matrix values, keeping track of all small sums wastes memory when we know they'll result in large differences.
Solution - Bounded Tracking:
class Solution:
def minimizeTheDifference(self, mat: List[List[int]], target: int) -> int:
possible_sums = {0}
min_possible = sum(min(row) for row in mat)
max_possible = sum(max(row) for row in mat)
# If target is outside possible range, calculate directly
if target <= min_possible:
return min_possible - target
if target >= max_possible:
return target - max_possible
for row in mat:
new_sums = set()
for current_sum in possible_sums:
for element in row:
new_sum = current_sum + element
# Only keep sums that could potentially improve our answer
# Skip sums that are already too far from target
if new_sum <= target * 2: # Heuristic boundary
new_sums.add(new_sum)
possible_sums = new_sums
return min(abs(sum_value - target) for sum_value in possible_sums)
These optimizations help prevent common runtime and memory issues while maintaining correctness of the solution.
Which data structure is used to implement priority queue?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!