1007. Minimum Domino Rotations For Equal Row
Problem Description
You have a row of dominoes where each domino has a top number and a bottom number (both ranging from 1 to 6). The dominoes are represented by two arrays: tops[i]
represents the top number of the i-th domino, and bottoms[i]
represents the bottom number of the i-th domino.
You can rotate any domino, which means swapping its top and bottom values. For example, if a domino has 2 on top and 5 on bottom, after rotating it will have 5 on top and 2 on bottom.
Your goal is to find the minimum number of rotations needed to make either:
- All values in the
tops
array the same, OR - All values in the
bottoms
array the same
If it's impossible to achieve either condition, return -1
.
For example, if you have dominoes with tops = [2,1,2,4,2,2]
and bottoms = [5,2,6,2,3,2]
, you could rotate some dominoes to make all top values equal to 2 or all bottom values equal to 2.
The key insight is that if a solution exists, the target value must appear in every domino (either on top or bottom). This means the target value must be either tops[0]
or bottoms[0]
, since these are the only values guaranteed to be present in at least the first domino.
Intuition
Let's think about what value could possibly be the target that appears on all tops or all bottoms after rotations.
For any value to appear on all positions (either all tops or all bottoms), that value must be present in every single domino - either on its top face or bottom face. If even one domino doesn't have the target value on either face, it's impossible to make that value appear in the required position through rotation.
This leads us to a crucial observation: the target value must appear in the first domino. Why? Because if the target value doesn't appear in the first domino (neither on top nor bottom), then we can never place that value in the first position, making it impossible to have all positions with the same value.
Since the first domino only has two values - tops[0]
and bottoms[0]
- these are the only two possible candidates for our target value. Any other value from 1 to 6 that doesn't appear in the first domino cannot be our answer.
Once we identify that only tops[0]
and bottoms[0]
are viable candidates, we need to check each one:
- For each candidate value
x
, verify if it appears in every domino (either top or bottom) - If it does appear in every domino, count how many dominoes already have
x
on top and how many havex
on bottom - To minimize rotations, we choose to make all values equal to
x
in the position (top or bottom) wherex
appears more frequently - The number of rotations needed would be
n - max(count_in_tops, count_in_bottoms)
If neither tops[0]
nor bottoms[0]
can be placed in all positions, the task is impossible and we return -1
.
Learn more about Greedy patterns.
Solution Approach
The solution implements a greedy approach using a helper function f(x)
that calculates the minimum rotations needed to make all values equal to x
.
Helper Function f(x)
:
The function takes a target value x
and determines how many rotations are needed to make either all tops or all bottoms equal to x
.
-
Initialize two counters:
cnt1
andcnt2
to track occurrences ofx
in tops and bottoms respectively -
Iterate through each domino pair
(a, b)
wherea = tops[i]
andb = bottoms[i]
-
For each domino:
- If
x
is not present in eithera
orb
, return infinity (inf
) since it's impossible to placex
in this position - If
a == x
, incrementcnt1
(counting how many dominoes already havex
on top) - If
b == x
, incrementcnt2
(counting how many dominoes already havex
on bottom)
- If
-
After checking all dominoes, calculate the minimum rotations:
- We can either make all tops equal to
x
or all bottoms equal tox
- If we want all tops to be
x
, we needn - cnt1
rotations (rotate dominoes that don't havex
on top) - If we want all bottoms to be
x
, we needn - cnt2
rotations (rotate dominoes that don't havex
on bottom) - Return
len(tops) - max(cnt1, cnt2)
to get the minimum rotations needed
- We can either make all tops equal to
Main Algorithm:
- Call
f(tops[0])
to check if we can make all values equal totops[0]
- Call
f(bottoms[0])
to check if we can make all values equal tobottoms[0]
- Take the minimum of these two results:
ans = min(f(tops[0]), f(bottoms[0]))
- If
ans == inf
, it means neither value can be placed in all positions, so return-1
- Otherwise, return
ans
as the minimum number of rotations
The time complexity is O(n)
where n
is the number of dominoes, as we iterate through the array at most twice (once for each candidate value). The space complexity is O(1)
as we only use a constant amount of extra space.
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 with tops = [2,1,2,4]
and bottoms = [5,2,6,2]
.
Step 1: Identify candidates The only possible target values are from the first domino:
tops[0] = 2
bottoms[0] = 5
Step 2: Check if we can make all values equal to 2
Call f(2)
:
- Domino 0:
(top=2, bottom=5)
β 2 is present (on top),cnt1=1, cnt2=0
- Domino 1:
(top=1, bottom=2)
β 2 is present (on bottom),cnt1=1, cnt2=1
- Domino 2:
(top=2, bottom=6)
β 2 is present (on top),cnt1=2, cnt2=1
- Domino 3:
(top=4, bottom=2)
β 2 is present (on bottom),cnt1=2, cnt2=2
All dominoes contain 2, so it's possible!
- To make all tops = 2: need
4 - 2 = 2
rotations (rotate dominoes 1 and 3) - To make all bottoms = 2: need
4 - 2 = 2
rotations (rotate dominoes 0 and 2) - Minimum rotations for value 2:
4 - max(2,2) = 2
Step 3: Check if we can make all values equal to 5
Call f(5)
:
- Domino 0:
(top=2, bottom=5)
β 5 is present (on bottom),cnt1=0, cnt2=1
- Domino 1:
(top=1, bottom=2)
β 5 is NOT present β returninf
Since 5 doesn't appear in domino 1, it's impossible to make all values equal to 5.
Step 4: Calculate final answer
ans = min(f(2), f(5)) = min(2, inf) = 2
- Since
ans β inf
, return2
The minimum number of rotations needed is 2. We can either:
- Rotate dominoes 1 and 3 to make all tops = 2:
[2,2,2,2]
- Or rotate dominoes 0 and 2 to make all bottoms = 2:
[2,2,2,2]
Solution Implementation
1class Solution:
2 def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int:
3 def calculate_min_rotations(target_value: int) -> int:
4 """
5 Calculate minimum rotations needed to make all values in one row equal to target_value.
6
7 Args:
8 target_value: The value we want all dominoes to show in one row
9
10 Returns:
11 Minimum rotations needed, or inf if impossible
12 """
13 # Count occurrences of target_value in top and bottom rows
14 top_count = 0
15 bottom_count = 0
16
17 # Check each domino position
18 for top_val, bottom_val in zip(tops, bottoms):
19 # If target_value is not present in either top or bottom, it's impossible
20 if target_value not in (top_val, bottom_val):
21 return float('inf')
22
23 # Count how many times target_value appears in each row
24 top_count += (top_val == target_value)
25 bottom_count += (bottom_val == target_value)
26
27 # Return minimum rotations needed
28 # We can either rotate tops to bottom or bottoms to top
29 # Choose the row with more target values (rotate less)
30 return len(tops) - max(top_count, bottom_count)
31
32 # Only need to check values from first domino
33 # If a solution exists, it must use either tops[0] or bottoms[0]
34 min_rotations = min(calculate_min_rotations(tops[0]),
35 calculate_min_rotations(bottoms[0]))
36
37 # Return -1 if no valid solution exists, otherwise return minimum rotations
38 return -1 if min_rotations == float('inf') else min_rotations
39
1class Solution {
2 private int dominoCount;
3 private int[] topValues;
4 private int[] bottomValues;
5
6 public int minDominoRotations(int[] tops, int[] bottoms) {
7 // Initialize instance variables
8 dominoCount = tops.length;
9 this.topValues = tops;
10 this.bottomValues = bottoms;
11
12 // Try to make all dominoes show either the first top value or first bottom value
13 // We only need to check these two values because if a solution exists,
14 // at least one of these values must appear on every domino
15 int rotationsForFirstTop = calculateRotations(tops[0]);
16 int rotationsForFirstBottom = calculateRotations(bottoms[0]);
17
18 // Get the minimum rotations needed
19 int minRotations = Math.min(rotationsForFirstTop, rotationsForFirstBottom);
20
21 // If minimum rotations exceeds domino count, it's impossible
22 return minRotations > dominoCount ? -1 : minRotations;
23 }
24
25 /**
26 * Calculate minimum rotations needed to make all dominoes show the target value
27 * @param targetValue the value we want all dominoes to show
28 * @return minimum rotations needed, or dominoCount + 1 if impossible
29 */
30 private int calculateRotations(int targetValue) {
31 int topCount = 0; // Count of dominoes already showing targetValue on top
32 int bottomCount = 0; // Count of dominoes already showing targetValue on bottom
33
34 // Check each domino
35 for (int i = 0; i < dominoCount; i++) {
36 // If targetValue doesn't appear on either side of this domino,
37 // it's impossible to make all dominoes show targetValue
38 if (topValues[i] != targetValue && bottomValues[i] != targetValue) {
39 return dominoCount + 1; // Return impossible marker
40 }
41
42 // Count occurrences of targetValue on top and bottom
43 topCount += (topValues[i] == targetValue) ? 1 : 0;
44 bottomCount += (bottomValues[i] == targetValue) ? 1 : 0;
45 }
46
47 // The minimum rotations needed is total dominoes minus
48 // the maximum count already in correct position
49 // We choose the side with more targetValues to minimize rotations
50 return dominoCount - Math.max(topCount, bottomCount);
51 }
52}
53
1class Solution {
2public:
3 int minDominoRotations(vector<int>& tops, vector<int>& bottoms) {
4 int n = tops.size();
5
6 // Lambda function to calculate minimum rotations needed to make all dominoes show value x
7 auto calculateRotations = [&](int targetValue) {
8 int topCount = 0; // Count of dominoes with targetValue on top
9 int bottomCount = 0; // Count of dominoes with targetValue on bottom
10
11 // Check each domino position
12 for (int i = 0; i < n; ++i) {
13 // If targetValue is not present on either side, it's impossible
14 if (tops[i] != targetValue && bottoms[i] != targetValue) {
15 return n + 1; // Return impossible value
16 }
17
18 // Count occurrences of targetValue
19 if (tops[i] == targetValue) {
20 topCount++;
21 }
22 if (bottoms[i] == targetValue) {
23 bottomCount++;
24 }
25 }
26
27 // Minimum rotations = total dominoes - max occurrences on one side
28 return n - max(topCount, bottomCount);
29 };
30
31 // Try with the values from the first domino (only possible candidates)
32 int result = min(calculateRotations(tops[0]), calculateRotations(bottoms[0]));
33
34 // If result is greater than n, it's impossible
35 return result > n ? -1 : result;
36 }
37};
38
1/**
2 * Finds the minimum number of domino rotations to make all values in either top or bottom row the same
3 * @param tops - Array representing the top values of dominoes
4 * @param bottoms - Array representing the bottom values of dominoes
5 * @returns Minimum number of rotations needed, or -1 if impossible
6 */
7function minDominoRotations(tops: number[], bottoms: number[]): number {
8 const dominoCount: number = tops.length;
9
10 /**
11 * Calculates minimum rotations needed to make all dominoes show targetValue on one side
12 * @param targetValue - The value we want all dominoes to show
13 * @returns Minimum rotations needed, or dominoCount + 1 if impossible
14 */
15 const calculateMinRotations = (targetValue: number): number => {
16 let topMatchCount: number = 0;
17 let bottomMatchCount: number = 0;
18
19 // Check each domino position
20 for (let i = 0; i < dominoCount; ++i) {
21 // If neither top nor bottom has the target value, it's impossible
22 if (tops[i] !== targetValue && bottoms[i] !== targetValue) {
23 return dominoCount + 1;
24 }
25
26 // Count occurrences of target value in top row
27 topMatchCount += tops[i] === targetValue ? 1 : 0;
28
29 // Count occurrences of target value in bottom row
30 bottomMatchCount += bottoms[i] === targetValue ? 1 : 0;
31 }
32
33 // Return minimum rotations needed (rotate the row with fewer target values)
34 return dominoCount - Math.max(topMatchCount, bottomMatchCount);
35 };
36
37 // Try both possible target values (from first domino's top and bottom)
38 const minRotations: number = Math.min(
39 calculateMinRotations(tops[0]),
40 calculateMinRotations(bottoms[0])
41 );
42
43 // Return -1 if impossible, otherwise return minimum rotations
44 return minRotations > dominoCount ? -1 : minRotations;
45}
46
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the arrays tops
and bottoms
.
The algorithm calls the helper function f(x)
at most twice - once with tops[0]
and once with bottoms[0]
. Each call to f(x)
iterates through all elements using zip(tops, bottoms)
, which takes O(n)
time. Within each iteration, the operations (checking if x
is in (a, b)
, incrementing counters, and comparisons) all take O(1)
time. Therefore, the total time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. The helper function f(x)
uses two counter variables (cnt1
and cnt2
), and the main function uses one variable ans
to store the result. The zip
function creates an iterator that doesn't store all pairs in memory at once. All these variables occupy constant space regardless of the input size, resulting in O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Double Counting When Both Values Are the Same
The Problem:
When a domino has the same value on both top and bottom (e.g., tops[i] = bottoms[i] = 3
), the current counting logic increments both top_count
and bottom_count
. This leads to incorrect calculation of minimum rotations.
Example:
tops = [3, 5, 3] bottoms = [3, 3, 5]
For target value 3:
- Position 0: Both are 3, so
top_count = 1, bottom_count = 1
- Position 1: Only bottom is 3, so
top_count = 1, bottom_count = 2
- Position 2: Only top is 3, so
top_count = 2, bottom_count = 2
The function returns 3 - max(2, 2) = 1
, but actually no rotations are needed since we can already have all bottoms as 3.
The Solution: When counting, we should handle the case where both values are the same specially:
def calculate_min_rotations(target_value: int) -> int:
top_count = 0
bottom_count = 0
for top_val, bottom_val in zip(tops, bottoms):
if target_value not in (top_val, bottom_val):
return float('inf')
# Handle the case where both values are the same
if top_val == bottom_val == target_value:
# This domino contributes to both rows without rotation
continue
elif top_val == target_value:
top_count += 1
elif bottom_val == target_value:
bottom_count += 1
# Calculate rotations needed for each scenario
n = len(tops)
# To make all tops equal to target_value
rotations_for_tops = bottom_count
# To make all bottoms equal to target_value
rotations_for_bottoms = top_count
return min(rotations_for_tops, rotations_for_bottoms)
Pitfall 2: Not Understanding the Rotation Logic
The Problem:
The formula len(tops) - max(top_count, bottom_count)
can be confusing and may lead to misunderstanding of what rotation actually means.
Clearer Alternative: Instead of thinking about maximizing counts, think directly about the rotations needed:
def calculate_min_rotations(target_value: int) -> int:
rotations_to_top = 0 # Rotations to make all tops = target_value
rotations_to_bottom = 0 # Rotations to make all bottoms = target_value
for top_val, bottom_val in zip(tops, bottoms):
if target_value not in (top_val, bottom_val):
return float('inf')
# Count rotations needed for each strategy
if top_val != target_value:
rotations_to_top += 1 # Need to rotate this domino to get target on top
if bottom_val != target_value:
rotations_to_bottom += 1 # Need to rotate this domino to get target on bottom
return min(rotations_to_top, rotations_to_bottom)
This approach is more intuitive: directly count how many dominoes need rotation for each strategy, then return the minimum.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!