3122. Minimum Number of Operations to Satisfy Conditions
Problem Description
You have a 2D matrix grid
with dimensions m x n
. Your task is to transform this matrix so that it satisfies two specific conditions:
-
Column Uniformity: All cells in the same column must have the same value. In other words, for any cell at position
(i, j)
, it must equal the cell directly below it:grid[i][j] == grid[i + 1][j]
(when the cell below exists). -
Adjacent Column Difference: Values in adjacent columns must be different. For any cell at position
(i, j)
, it must be different from the cell directly to its right:grid[i][j] != grid[i][j + 1]
(when the cell to the right exists).
To achieve this configuration, you can perform operations where you change any cell's value to any non-negative number. Each change counts as one operation.
The goal is to find the minimum number of operations needed to transform the grid so that both conditions are met.
For example, if you have a grid where the first column needs to all become 2's and the second column needs to all become 5's (ensuring they're different), you would count how many cells need to be changed from their original values to achieve this pattern.
Intuition
Since each column must have all cells containing the same value, we need to pick one value for each entire column. The key insight is that we're looking for the optimal value to assign to each column while ensuring adjacent columns have different values.
First, let's think about what happens when we fix a value for a column. If we decide column i
should have value v
, then the number of operations needed for that column equals the total cells in the column minus the cells already containing v
. For instance, if a column has 5 cells total and 2 of them already contain the value 3, then choosing 3 for that column requires 5 - 2 = 3
operations.
The constraint that adjacent columns must have different values creates a dependency between columns. This suggests a dynamic programming approach where we make decisions column by column from left to right.
Since the problem states we can change any cell to any non-negative number, but the original grid contains limited values (typically 0-9 based on the solution), we can reasonably assume we only need to consider changing columns to these existing values. Why? Because changing to a value that already appears frequently in a column minimizes operations for that column.
This leads us to define our state as: "What's the minimum cost to process the first i
columns, where column i
is set to value j
?" We can then build up the solution by considering each column and each possible value (0-9), always ensuring we don't pick the same value as the previous column.
For each column position and value combination, we look back at the previous column's minimum costs for all different values, add the cost of setting the current column to our chosen value, and keep track of the minimum. This way, we systematically explore all valid configurations while maintaining the constraint that adjacent columns differ.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement a dynamic programming solution where we process columns from left to right, tracking the minimum operations needed for each possible value assignment.
State Definition:
We define f[i][j]
as the minimum number of operations needed to process columns 0
through i
, where column i
is assigned the value j
. Since we only consider values 0-9, we create a 2D DP table with dimensions n × 10
where n
is the number of columns.
Preprocessing Each Column:
For each column i
, we first count how many cells already contain each value (0-9). This is stored in cnt[j]
, which tells us how many cells in column i
already have value j
. If we choose value j
for this column, we need m - cnt[j]
operations (where m
is the number of rows).
Base Case: For the first column (i = 0), we can choose any value. The cost is simply:
f[0][j] = m - cnt[j]
State Transition: For subsequent columns (i > 0), we must ensure the chosen value differs from the previous column's value. The transition formula is:
f[i][j] = min(f[i-1][k] + m - cnt[j]) for all k ≠ j
This means: to set column i
to value j
, we look at all possible values k
(where k ≠ j
) that could have been used in column i-1
, add their minimum cost f[i-1][k]
to the cost of changing column i
to value j
(which is m - cnt[j]
), and take the minimum among all such combinations.
Final Answer:
After processing all columns, the answer is the minimum value among all f[n-1][j]
for j from 0 to 9. This represents the minimum operations needed where the last column can be any value.
Implementation Details:
- Initialize the DP table with infinity (
inf
) to represent uncomputed states - For each column, count occurrences of each value using the original grid
- Apply the DP transitions column by column
- Return
min(f[-1])
which finds the minimum among all possible values for the last column
The time complexity is O(n × m × 100) since we have n columns, m rows for counting, and 10 × 10 value comparisons per column. The space complexity is O(n × 10) for the DP table.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Consider this 3×3 grid:
grid = [[1, 0, 2], [1, 0, 2], [1, 1, 2]]
We need to transform this grid so that:
- All cells in each column have the same value
- Adjacent columns have different values
Step 1: Analyze each column
Column 0: Contains [1, 1, 1] - already uniform with value 1 (3 cells with 1) Column 1: Contains [0, 0, 1] - has 2 cells with 0, 1 cell with 1 Column 2: Contains [2, 2, 2] - already uniform with value 2 (3 cells with 2)
Step 2: Set up DP table
We'll create f[i][j]
where i represents columns (0-2) and j represents values (0-9).
Step 3: Initialize first column (i=0)
For column 0, count occurrences:
- Value 1 appears 3 times
- All other values appear 0 times
So f[0][j]
= 3 - count[j]:
f[0][0]
= 3 - 0 = 3 (need to change all 3 cells to 0)f[0][1]
= 3 - 3 = 0 (no changes needed, already all 1s)f[0][2]
= 3 - 0 = 3 (need to change all 3 cells to 2)- ... (all other values would need 3 operations)
Step 4: Process column 1 (i=1)
For column 1, count occurrences:
- Value 0 appears 2 times
- Value 1 appears 1 time
- Other values appear 0 times
For each possible value j for column 1:
-
If j=0: Cost = 3 - 2 = 1 operation
- Must look at f[0][k] where k ≠ 0
- Best previous: f[0][1] = 0
- Total: f[1][0] = 0 + 1 = 1
-
If j=1: Cost = 3 - 1 = 2 operations
- Must look at f[0][k] where k ≠ 1
- Best previous: f[0][0] = 3 or f[0][2] = 3 (minimum is 3)
- Total: f[1][1] = 3 + 2 = 5
-
If j=2: Cost = 3 - 0 = 3 operations
- Must look at f[0][k] where k ≠ 2
- Best previous: f[0][1] = 0
- Total: f[1][2] = 0 + 3 = 3
Step 5: Process column 2 (i=2)
For column 2, count occurrences:
- Value 2 appears 3 times
- Other values appear 0 times
For each possible value j for column 2:
-
If j=0: Cost = 3 - 0 = 3 operations
- Must look at f[1][k] where k ≠ 0
- Best previous: f[1][2] = 3
- Total: f[2][0] = 3 + 3 = 6
-
If j=1: Cost = 3 - 0 = 3 operations
- Must look at f[1][k] where k ≠ 1
- Best previous: f[1][0] = 1
- Total: f[2][1] = 1 + 3 = 4
-
If j=2: Cost = 3 - 3 = 0 operations
- Must look at f[1][k] where k ≠ 2
- Best previous: f[1][0] = 1
- Total: f[2][2] = 1 + 0 = 1
Step 6: Find the answer
The minimum value in the last column is min(f[2]) = min(6, 4, 1, ...) = 1
Therefore, the minimum number of operations needed is 1.
The optimal transformation would be:
- Keep column 0 as all 1s (0 operations)
- Change column 1 to all 0s (1 operation: change the bottom cell from 1 to 0)
- Keep column 2 as all 2s (0 operations)
Final grid: [[1, 0, 2], [1, 0, 2], [1, 0, 2]]
This satisfies both conditions: each column is uniform, and adjacent columns have different values (1≠0, 0≠2).
Solution Implementation
1class Solution:
2 def minimumOperations(self, grid: List[List[int]]) -> int:
3 # Get dimensions of the grid
4 num_rows, num_cols = len(grid), len(grid[0])
5
6 # Initialize DP table: dp[col][val] = minimum operations to make
7 # columns 0 to col valid, with column col having value val
8 dp = [[float('inf')] * 10 for _ in range(num_cols)]
9
10 # Process each column
11 for col in range(num_cols):
12 # Count frequency of each value (0-9) in current column
13 value_count = [0] * 10
14 for row in range(num_rows):
15 value_count[grid[row][col]] += 1
16
17 if col == 0:
18 # Base case: first column can be any value
19 # Operations needed = total cells - cells already having target value
20 for target_value in range(10):
21 dp[col][target_value] = num_rows - value_count[target_value]
22 else:
23 # For subsequent columns, consider all possible values
24 for current_value in range(10):
25 # Try all possible values for previous column
26 for prev_value in range(10):
27 # Adjacent columns must have different values
28 if prev_value != current_value:
29 # Update minimum operations needed
30 operations_needed = dp[col - 1][prev_value] + num_rows - value_count[current_value]
31 dp[col][current_value] = min(dp[col][current_value], operations_needed)
32
33 # Return minimum operations across all possible values for last column
34 return min(dp[-1])
35
1class Solution {
2 public int minimumOperations(int[][] grid) {
3 int numRows = grid.length;
4 int numCols = grid[0].length;
5
6 // dp[col][value] = minimum operations to make columns 0 to col valid,
7 // where column col has all elements equal to value
8 int[][] dp = new int[numCols][10];
9
10 // Initialize with a large value representing infinity
11 final int INFINITY = 1 << 29;
12 for (int[] row : dp) {
13 Arrays.fill(row, INFINITY);
14 }
15
16 // Process each column
17 for (int col = 0; col < numCols; col++) {
18 // Count frequency of each value (0-9) in current column
19 int[] frequency = new int[10];
20 for (int row = 0; row < numRows; row++) {
21 frequency[grid[row][col]]++;
22 }
23
24 if (col == 0) {
25 // Base case: first column
26 // Operations needed = total elements - elements already equal to target value
27 for (int targetValue = 0; targetValue < 10; targetValue++) {
28 dp[col][targetValue] = numRows - frequency[targetValue];
29 }
30 } else {
31 // For subsequent columns, consider all possible values for current column
32 for (int currentValue = 0; currentValue < 10; currentValue++) {
33 // Try all possible values for previous column
34 for (int previousValue = 0; previousValue < 10; previousValue++) {
35 // Adjacent columns must have different values
36 if (previousValue != currentValue) {
37 // Update minimum operations: previous column operations + current column operations
38 dp[col][currentValue] = Math.min(
39 dp[col][currentValue],
40 dp[col - 1][previousValue] + numRows - frequency[currentValue]
41 );
42 }
43 }
44 }
45 }
46 }
47
48 // Find minimum operations among all possible values for the last column
49 int minOperations = INFINITY;
50 for (int value = 0; value < 10; value++) {
51 minOperations = Math.min(minOperations, dp[numCols - 1][value]);
52 }
53
54 return minOperations;
55 }
56}
57
1class Solution {
2public:
3 int minimumOperations(vector<vector<int>>& grid) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // dp[col][value] = minimum operations to make first 'col+1' columns valid
8 // where column 'col' has all elements equal to 'value'
9 int dp[cols][10];
10 memset(dp, 0x3f, sizeof(dp)); // Initialize with large values
11
12 // Process each column
13 for (int col = 0; col < cols; ++col) {
14 // Count frequency of each value (0-9) in current column
15 int frequency[10] = {0};
16 for (int row = 0; row < rows; ++row) {
17 ++frequency[grid[row][col]];
18 }
19
20 if (col == 0) {
21 // Base case: first column
22 // Operations needed = total elements - elements already equal to target value
23 for (int targetValue = 0; targetValue < 10; ++targetValue) {
24 dp[col][targetValue] = rows - frequency[targetValue];
25 }
26 } else {
27 // For subsequent columns, consider all possible values
28 for (int currentValue = 0; currentValue < 10; ++currentValue) {
29 // Try all possible values for previous column
30 for (int prevValue = 0; prevValue < 10; ++prevValue) {
31 // Adjacent columns must have different values
32 if (prevValue != currentValue) {
33 // Update minimum operations needed
34 dp[col][currentValue] = min(dp[col][currentValue],
35 dp[col - 1][prevValue] + rows - frequency[currentValue]);
36 }
37 }
38 }
39 }
40 }
41
42 // Find minimum operations among all possible values for last column
43 return *min_element(dp[cols - 1], dp[cols - 1] + 10);
44 }
45};
46
1/**
2 * Finds the minimum number of operations to make all columns have different values
3 * @param grid - 2D array where each column should have all same values, and adjacent columns should differ
4 * @returns Minimum number of operations needed
5 */
6function minimumOperations(grid: number[][]): number {
7 const numRows: number = grid.length;
8 const numCols: number = grid[0].length;
9
10 // dp[col][digit] = minimum operations to make column 'col' all equal to 'digit'
11 // considering all columns from 0 to col
12 const dp: number[][] = Array.from({ length: numCols }, () =>
13 Array.from({ length: 10 }, () => Infinity)
14 );
15
16 // Process each column
17 for (let col = 0; col < numCols; col++) {
18 // Count frequency of each digit (0-9) in current column
19 const digitFrequency: number[] = Array(10).fill(0);
20 for (let row = 0; row < numRows; row++) {
21 digitFrequency[grid[row][col]]++;
22 }
23
24 if (col === 0) {
25 // Base case: first column
26 // Cost to make all elements in column equal to digit j
27 for (let targetDigit = 0; targetDigit < 10; targetDigit++) {
28 dp[col][targetDigit] = numRows - digitFrequency[targetDigit];
29 }
30 } else {
31 // Recursive case: subsequent columns
32 // For each possible target digit in current column
33 for (let currentDigit = 0; currentDigit < 10; currentDigit++) {
34 // Try all different digits from previous column
35 for (let prevDigit = 0; prevDigit < 10; prevDigit++) {
36 // Adjacent columns must have different values
37 if (currentDigit !== prevDigit) {
38 // Operations = previous column cost + current column conversion cost
39 const totalOperations: number = dp[col - 1][prevDigit] +
40 (numRows - digitFrequency[currentDigit]);
41 dp[col][currentDigit] = Math.min(dp[col][currentDigit], totalOperations);
42 }
43 }
44 }
45 }
46 }
47
48 // Return minimum operations from last column across all possible digits
49 return Math.min(...dp[numCols - 1]);
50}
51
Time and Space Complexity
Time Complexity: O(n × (m + C²))
The algorithm iterates through each column (n
columns total). For each column:
- It counts the occurrences of each value in that column, which takes
O(m)
time since there arem
rows - It then performs dynamic programming transitions, which involves two nested loops over
C
possible values (whereC = 10
), resulting inO(C²)
operations
Therefore, the total time complexity is O(n × (m + C²))
.
Space Complexity: O(n × C)
The algorithm uses a 2D DP array f
with dimensions n × 10
, where:
n
is the number of columns in the gridC = 10
represents the possible values (0-9)
Additionally, there's a temporary array cnt
of size C = 10
for counting, but this doesn't affect the overall space complexity.
Therefore, the space complexity is O(n × C)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Value Range Assumption
One of the most common mistakes is assuming the grid values are limited to 0-9 without verifying the problem constraints. The code hardcodes 10 as the maximum value range, but if the actual values in the grid can be larger (e.g., 0-99 or 0-999), the solution will fail.
Problem Example:
grid = [[15, 20], [15, 25], [15, 30]] # The code will crash with IndexError since value_count only has 10 elements
Solution: First scan the grid to determine the actual range of values, or check the problem constraints:
def minimumOperations(self, grid: List[List[int]]) -> int:
num_rows, num_cols = len(grid), len(grid[0])
# Find the actual range of values in the grid
max_val = max(max(row) for row in grid)
num_values = max_val + 1
# Use dynamic sizing instead of hardcoded 10
dp = [[float('inf')] * num_values for _ in range(num_cols)]
for col in range(num_cols):
value_count = [0] * num_values
for row in range(num_rows):
value_count[grid[row][col]] += 1
# Rest of the logic remains the same but uses num_values instead of 10
2. Memory Optimization Opportunity Missed
The current solution uses O(n × k) space where k is the number of possible values. Since we only need the previous column's results to compute the current column, we can optimize space to O(k).
Optimized Solution:
def minimumOperations(self, grid: List[List[int]]) -> int:
num_rows, num_cols = len(grid), len(grid[0])
# Only need previous and current column states
prev_dp = [float('inf')] * 10
for col in range(num_cols):
value_count = [0] * 10
for row in range(num_rows):
value_count[grid[row][col]] += 1
curr_dp = [float('inf')] * 10
if col == 0:
for target_value in range(10):
curr_dp[target_value] = num_rows - value_count[target_value]
else:
for current_value in range(10):
for prev_value in range(10):
if prev_value != current_value:
operations_needed = prev_dp[prev_value] + num_rows - value_count[current_value]
curr_dp[current_value] = min(curr_dp[current_value], operations_needed)
prev_dp = curr_dp
return min(prev_dp)
3. Inefficient Minimum Finding for Transitions
When computing f[i][j]
, we iterate through all k ≠ j to find the minimum. For large value ranges, this becomes inefficient. We can optimize by precomputing the two smallest values from the previous column.
Optimized Transition:
# Find the minimum and second minimum from previous column
min_val = min(prev_dp)
min_idx = prev_dp.index(min_val)
second_min = min(prev_dp[i] for i in range(10) if i != min_idx)
for current_value in range(10):
# Use minimum unless it conflicts with current value
if current_value != min_idx:
curr_dp[current_value] = min_val + num_rows - value_count[current_value]
else:
curr_dp[current_value] = second_min + num_rows - value_count[current_value]
This reduces the inner loop complexity from O(k²) to O(k) per column.
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
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!