Facebook Pixel

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:

  1. 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).

  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 Evaluator

Example 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 are m rows
  • It then performs dynamic programming transitions, which involves two nested loops over C possible values (where C = 10), resulting in O(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 grid
  • C = 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More