Minimum Domino Rotations For Equal Row
MedPrompt
In a row of dominoes, tops[i] and bottoms[i] represent the top and bottom halves of the ith domino. (A domino is a tile with two numbers from 1 to 6 - one on each half of the tile.)
We may rotate the ith domino, so that tops[i] and bottoms[i] swap values.
Return the minimum number of rotations so that all the values in tops are the same, or all the values in bottoms are the same.
If it cannot be done, return -1.
Example 1:
Input: tops = [2,1,2,4,2,2], bottoms = [5,2,6,2,3,2]
Output: 2
Explanation:
The first figure represents the dominoes as given by tops and bottoms: before we do any rotations.
If we rotate the second and fourth dominoes, we can make every value in the top row equal to 2, as indicated by the second figure.Example 2:
Input: tops = [3,5,1,2,3], bottoms = [3,6,3,3,4]
Output: -1
Explanation:
In this case, it is not possible to rotate the dominoes to make one row of values equal.
Constraints:
2 <= tops.length <= 2 * 104bottoms.length == tops.length1 <= tops[i], bottoms[i] <= 6
Approaches
3 approaches with complexity analysis and trade-offs.
This approach explores every possible configuration of the domino row. For each of the N dominoes, we can either keep it as is or rotate it. This leads to 2^N possible states. We can use recursion (backtracking) to generate all these states. For each state, we check if either the top row or the bottom row consists of all equal values. We keep track of the minimum number of rotations used to achieve such a state.
Algorithm
- Initialize a global variable
minRotationsto a very large value (e.g.,Integer.MAX_VALUE). - Create a recursive function
solve(index, tops, bottoms, rotations): a. Base Case: Ifindex == tops.length: i. Check if thetopsrow is uniform. If yes, updateminRotations = min(minRotations, rotations). ii. Check if thebottomsrow is uniform. If yes, updateminRotations = min(minRotations, rotations). iii. Return. b. Recursive Step: i. Choice 1 (No rotation): Callsolve(index + 1, tops, bottoms, rotations). ii. Choice 2 (Rotation): - Swaptops[index]andbottoms[index]. - Callsolve(index + 1, tops, bottoms, rotations + 1). - Backtrack: Swaptops[index]andbottoms[index]back to their original values. - Call
solve(0, tops, bottoms, 0). - If
minRotationsis still the initial large value, return -1. Otherwise, returnminRotations.
Walkthrough
We define a recursive function, say findMinRotations(index, currentRotations), which explores the possibilities from the index-th domino onwards.
The base case for the recursion is when index reaches the end of the array (tops.length). At this point, we have a complete configuration. We then check if the tops array is uniform or if the bottoms array is uniform.
To check for uniformity, we can take the first element of a row and see if all other elements are the same.
If a row is uniform, we update a global minimum rotations variable with currentRotations if it's smaller.
In the recursive step for a given index, we have two choices:
- Don't rotate: We make a recursive call
findMinRotations(index + 1, currentRotations). - Rotate: We swap
tops[index]andbottoms[index], and then make a recursive callfindMinRotations(index + 1, currentRotations + 1). After the call returns, we must swap back (backtrack) to restore the state for other branches of the recursion. The initial call would befindMinRotations(0, 0). The final result is the global minimum found, or -1 if it was never updated.
// This approach is too slow and will result in a Time Limit Exceeded error.// It is for demonstration of the brute-force concept only.class Solution { int minRotations = Integer.MAX_VALUE; public int minDominoRotations(int[] tops, int[] bottoms) { solve(0, tops, bottoms, 0); return minRotations == Integer.MAX_VALUE ? -1 : minRotations; } private void solve(int index, int[] tops, int[] bottoms, int rotations) { if (index == tops.length) { checkUniform(tops, bottoms, rotations); return; } // Option 1: Don't rotate solve(index + 1, tops, bottoms, rotations); // Option 2: Rotate swap(tops, bottoms, index); solve(index + 1, tops, bottoms, rotations + 1); swap(tops, bottoms, index); // Backtrack } private void checkUniform(int[] tops, int[] bottoms, int rotations) { boolean topsUniform = true; for (int i = 1; i < tops.length; i++) { if (tops[i] != tops[0]) { topsUniform = false; break; } } if (topsUniform) { minRotations = Math.min(minRotations, rotations); } boolean bottomsUniform = true; for (int i = 1; i < bottoms.length; i++) { if (bottoms[i] != bottoms[0]) { bottomsUniform = false; break; } } if (bottomsUniform) { minRotations = Math.min(minRotations, rotations); } } private void swap(int[] tops, int[] bottoms, int i) { int temp = tops[i]; tops[i] = bottoms[i]; bottoms[i] = temp; }}Complexity
Time
O(N * 2^N). The recursion tree has `2^N` leaves. At each leaf node (base case), we perform a check that takes `O(N)` time.
Space
O(N). This is due to the recursion stack depth, which can go up to `N`.
Trade-offs
Pros
It's a straightforward translation of the problem statement: "try all possibilities".
Guaranteed to find the correct answer.
Cons
Extremely inefficient and will not pass the time limits for the given constraints.
Since the domino values are constrained to be between 1 and 6, the final uniform row must consist of one of these six numbers. This approach iterates through each number from 1 to 6 and treats it as a potential target value. For each target, it calculates the minimum rotations required to make either the tops or bottoms row uniform with that target.
Algorithm
- Initialize
minRotationstoInteger.MAX_VALUE. - Loop
targetfrom 1 to 6. a. InitializerotationsTop = 0,rotationsBottom = 0,possible = true. b. Loopifrom 0 totops.length - 1. i. Iftops[i] != targetandbottoms[i] != target, then thistargetis not possible. Setpossible = falseand break the inner loop. ii. Iftops[i] != target, incrementrotationsTop. iii. Ifbottoms[i] != target, incrementrotationsBottom. c. Ifpossibleis true, updateminRotations = min(minRotations, rotationsTop, rotationsBottom). - If
minRotationsis stillInteger.MAX_VALUE, return -1. Otherwise, returnminRotations.
Walkthrough
We can create a helper function, check(target, tops, bottoms), that determines the minimum rotations needed for a specific target value.
This function iterates through the dominoes from i = 0 to N-1. For each domino, it checks if the target value is present (either in tops[i] or bottoms[i]). If for any domino, the target is not present, it's impossible to make a uniform row with this target, so the function can return an indicator of failure (like Integer.MAX_VALUE).
While iterating, the function maintains two counters: rotationsTop (to make the top row uniform) and rotationsBottom (to make the bottom row uniform).
If tops[i] is not the target (but bottoms[i] is), we must rotate to make the top row uniform, so we increment rotationsTop.
Similarly, if bottoms[i] is not the target (but tops[i] is), we must rotate to make the bottom row uniform, so we increment rotationsBottom.
After checking all dominoes, the function returns the minimum of rotationsTop and rotationsBottom.
The main function calls this check helper for each number from 1 to 6, keeps track of the minimum result, and returns it. If no target value works, the minimum will remain at its initial large value, and we should return -1.
class Solution { public int minDominoRotations(int[] tops, int[] bottoms) { int minSwaps = Integer.MAX_VALUE; for (int target = 1; target <= 6; target++) { int currentSwaps = check(target, tops, bottoms); if (currentSwaps != -1) { minSwaps = Math.min(minSwaps, currentSwaps); } } return minSwaps == Integer.MAX_VALUE ? -1 : minSwaps; } // Helper to check for a specific target value private int check(int target, int[] tops, int[] bottoms) { int rotationsTop = 0; int rotationsBottom = 0; for (int i = 0; i < tops.length; i++) { if (tops[i] != target && bottoms[i] != target) { return -1; // Impossible for this target } else if (tops[i] != target) { rotationsTop++; } else if (bottoms[i] != target) { rotationsBottom++; } } return Math.min(rotationsTop, rotationsBottom); }}Complexity
Time
O(N). We iterate through the `N` dominoes for each of the 6 possible target values. The complexity is `6 * O(N)`, which simplifies to `O(N)`.
Space
O(1). We only use a few variables to store counts and the minimum value, regardless of the input size.
Trade-offs
Pros
Significantly faster than the brute-force approach.
Correct and handles all cases.
Relatively simple to reason about and implement.
Cons
It performs redundant checks. We know the target value must appear in the first domino, so checking all 6 values is not strictly necessary.
This is the most optimized approach. It's based on a crucial observation: if it's possible to make an entire row uniform with a value X, then X must be present in every single domino pair (tops[i], bottoms[i]). This means that the target value X must be present in the first domino pair, (tops[0], bottoms[0]). Therefore, we only need to check two potential candidates for the target value: tops[0] and bottoms[0].
Algorithm
- Define a helper function
check(target, tops, bottoms)that returns the minimum rotations to make a row equal totarget, or a large value/sentinel if impossible. a. Insidecheck, initializerotationsTop = 0,rotationsBottom = 0. b. Loopifrom 0 totops.length - 1. i. Iftops[i] != targetandbottoms[i] != target, returnInteger.MAX_VALUE. ii. Iftops[i] != target, incrementrotationsTop. iii. Ifbottoms[i] != target, incrementrotationsBottom. c. Returnmin(rotationsTop, rotationsBottom). - In the main function:
a. Call
rotations1 = check(tops[0], tops, bottoms). b. Callrotations2 = check(bottoms[0], tops, bottoms). c. Find the minimum ofrotations1androtations2. Let this beminRotations. - If
minRotationsisInteger.MAX_VALUE, return -1. Otherwise, returnminRotations.
Walkthrough
We can use the same helper function check(target, tops, bottoms) from the previous approach, which calculates the minimum rotations for a given target in O(N) time.
First, we try tops[0] as the target value. We call check(tops[0], tops, bottoms). This will give us the minimum rotations needed if tops[0] is the target, or an indicator of failure (e.g., -1 or Integer.MAX_VALUE) if it's not possible.
Next, we consider bottoms[0] as a potential target. If bottoms[0] is different from tops[0], we also call check(bottoms[0], tops, bottoms).
The final answer is the minimum of the valid results returned by these one or two calls. If both attempts fail, it's impossible to make any row uniform, so we return -1. This approach avoids iterating through all 6 possible values and directly tests the only viable candidates.
class Solution { public int minDominoRotations(int[] tops, int[] bottoms) { // Try making a row uniform with tops[0] int rotations1 = check(tops[0], tops, bottoms); // If tops[0] and bottoms[0] are the same, no need to check again. // Also, if the first check was successful, we don't need to check bottoms[0] // because the number of rotations would be the same or worse. // But for simplicity and correctness, we can check both and take the min. if (tops[0] == bottoms[0]) { return rotations1; } // Try making a row uniform with bottoms[0] int rotations2 = check(bottoms[0], tops, bottoms); // Return the minimum valid result. if (rotations1 == -1 && rotations2 == -1) { return -1; } else if (rotations1 == -1) { return rotations2; } else if (rotations2 == -1) { return rotations1; } else { return Math.min(rotations1, rotations2); } } // Helper to check for a specific target value // Returns min rotations or -1 if not possible private int check(int target, int[] tops, int[] bottoms) { int rotationsTop = 0; int rotationsBottom = 0; for (int i = 0; i < tops.length; i++) { if (tops[i] != target && bottoms[i] != target) { return -1; // Impossible for this target } else if (tops[i] != target) { rotationsTop++; } else if (bottoms[i] != target) { rotationsBottom++; } } return Math.min(rotationsTop, rotationsBottom); }}Complexity
Time
O(N). We perform at most two passes over the arrays. The complexity is `2 * O(N)`, which is `O(N)`.
Space
O(1). Constant extra space is used.
Trade-offs
Pros
This is the most efficient solution in terms of constant factors.
It correctly identifies that only two values need to be checked.
Cons
The core logic relies on an insight that might not be immediately obvious to everyone.
Solutions
Solution
class Solution {private int n;private int[] tops;private int[] bottoms;public int minDominoRotations(int[] tops, int[] bottoms) { n = tops.length; this.tops = tops; this.bottoms = bottoms; int ans = Math.min(f(tops[0]), f(bottoms[0])); return ans > n ? -1 : ans; }private int f(int x) { int cnt1 = 0, cnt2 = 0; for (int i = 0; i < n; ++i) { if (tops[i] != x && bottoms[i] != x) { return n + 1; } cnt1 += tops[i] == x ? 1 : 0; cnt2 += bottoms[i] == x ? 1 : 0; } return n - Math.max(cnt1, cnt2); }}class Solution {public: int minDominoRotations(vector<int> &tops, vector<int> &bottoms) { int n = tops.size(); auto f = [&](int x) { int cnt1 = 0, cnt2 = 0; for (int i = 0; i < n; ++i) { if (tops[i] != x && bottoms[i] != x) { return n + 1; } cnt1 += tops[i] == x; cnt2 += bottoms[i] == x; } return n - max(cnt1, cnt2); }; int ans = min(f(tops[0]), f(bottoms[0])); return ans > n ? -1 : ans; }};class Solution: def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int: def f(x: int) -> int: cnt1 = cnt2 = 0 for a, b in zip(tops, bottoms): if x not in (a, b): return inf cnt1 += a == x cnt2 += b == x return len(tops) - max(cnt1, cnt2) ans = min(f(tops[0]), f(bottoms[0])) return - 1 if ans == inf else ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.