Number of Equivalent Domino Pairs
EasyPrompt
Given a list of dominoes, dominoes[i] = [a, b] is equivalent to dominoes[j] = [c, d] if and only if either (a == c and b == d), or (a == d and b == c) - that is, one domino can be rotated to be equal to another domino.
Return the number of pairs (i, j) for which 0 <= i < j < dominoes.length, and dominoes[i] is equivalent to dominoes[j].
Example 1:
Input: dominoes = [[1,2],[2,1],[3,4],[5,6]]
Output: 1Example 2:
Input: dominoes = [[1,2],[1,2],[1,1],[1,2],[2,2]]
Output: 3
Constraints:
1 <= dominoes.length <= 4 * 104dominoes[i].length == 21 <= dominoes[i][j] <= 9
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves checking every possible pair of dominoes in the list to see if they are equivalent. It uses two nested loops to iterate through all pairs (i, j) where i < j.
Algorithm
- Initialize a variable
pairsto 0. - Use a nested loop to iterate through all unique pairs of indices
(i, j)wherei < j. - For each pair of dominoes
dominoes[i]anddominoes[j], check for equivalence. - Let
d1 = dominoes[i]andd2 = dominoes[j]. - The dominoes are equivalent if
(d1[0] == d2[0] && d1[1] == d2[1])or(d1[0] == d2[1] && d1[1] == d2[0]). - If they are equivalent, increment the
pairscounter. - After checking all pairs, return the final
pairscount.
Walkthrough
The brute-force method is the most straightforward way to solve the problem. We can compare every domino with every other domino that comes after it in the list.
We initialize a counter for the number of equivalent pairs to zero. The outer loop runs from the first domino to the second-to-last domino (index i from 0 to n-2). The inner loop runs from the domino immediately following i to the last domino (index j from i+1 to n-1). This ensures that we only consider each pair (i, j) once and that i is always less than j.
Inside the inner loop, we perform the equivalence check. A domino [a, b] is equivalent to [c, d] if they are identical or if one can be rotated to match the other. This translates to the condition (a == c && b == d) || (a == d && b == c). If this condition is true, we've found an equivalent pair, so we increment our counter. After the loops complete, the counter will hold the total number of such pairs.
class Solution { public int numEquivDominoPairs(int[][] dominoes) { int count = 0; int n = dominoes.length; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int[] d1 = dominoes[i]; int[] d2 = dominoes[j]; if ((d1[0] == d2[0] && d1[1] == d2[1]) || (d1[0] == d2[1] && d1[1] == d2[0])) { count++; } } } return count; }}Complexity
Time
O(N^2), where N is the number of dominoes. The nested loops lead to approximately N^2 / 2 comparisons, which is quadratic.
Space
O(1), as we only use a constant amount of extra space for the counter and loop variables.
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space, making it very memory-efficient.
Cons
Highly inefficient for large inputs due to its quadratic time complexity.
Will likely result in a 'Time Limit Exceeded' error on competitive programming platforms for the given constraints.
A much more efficient approach is to iterate through the list of dominoes just once. We can standardize each domino into a "canonical" form to make comparisons easier. For a domino [a, b], its canonical form can be [min(a, b), max(a, b)]. We then use a hash map or an array to count the occurrences of each canonical form.
Algorithm
- Initialize
pairs = 0and a frequency map (or an array)counts. - Iterate through each
dominoin thedominoeslist. - For each
domino, create a canonical representation. A simple way is to sort its values:v1 = min(domino[0], domino[1]),v2 = max(domino[0], domino[1]). - Create a unique key from this canonical form. Since values are from 1 to 9, a key can be
key = v1 * 10 + v2. - Look up the
keyin thecountsmap/array. Let the current frequency becurrentCount. - Each of the
currentCountdominoes seen so far can form a new pair with the current domino. AddcurrentCounttopairs. - Increment the frequency for the
keyin thecountsmap/array. - After the loop, return
pairs.
Walkthrough
To optimize from a quadratic to a linear time complexity, we need to avoid nested loops. We can achieve this by processing the list in a single pass and using a data structure to remember the dominoes we've already seen.
The key insight is to represent each domino in a standard, or canonical, form. Since [a, b] is equivalent to [b, a], we can define the canonical form as the pair with the smaller number first, i.e., [min(a, b), max(a, b)]. For example, both [1, 2] and [2, 1] map to the canonical form [1, 2].
We can then count the frequency of each canonical form. As we iterate through the dominoes, for each domino, we first convert it to its canonical form. Then, we check how many times we've already seen this canonical form. If we've seen it k times before, the current domino can form k new pairs. We add k to our total count of pairs and then update the frequency of this canonical form to k+1.
To implement the frequency count, we can use a hash map. Given the constraint that domino values are between 1 and 9, we can create a unique integer key for each canonical pair [v1, v2] by computing v1 * 10 + v2. This allows us to use a simple array of size 100 instead of a hash map, which can be slightly faster.
// Using an array as a frequency mapclass Solution { public int numEquivDominoPairs(int[][] dominoes) { int[] counts = new int[100]; // Keys are from 1*10+1=11 to 9*10+9=99 int pairs = 0; for (int[] domino : dominoes) { int v1 = Math.min(domino[0], domino[1]); int v2 = Math.max(domino[0], domino[1]); int key = v1 * 10 + v2; // The number of pairs the current domino forms is the number // of equivalent dominoes we've already seen. pairs += counts[key]; // Increment the count for this canonical form. counts[key]++; } return pairs; }}Complexity
Time
O(N), where N is the number of dominoes. We iterate through the `dominoes` array only once. Each operation (calculating key, accessing array/map) takes constant time.
Space
O(1). The keys in our frequency map are derived from numbers 1-9. The number of unique canonical pairs is fixed and small (45 pairs from `(1,1)` to `(9,9)`). Thus, the space for the map/array is constant regardless of the input size `N`.
Trade-offs
Pros
Highly efficient with a linear time complexity, making it suitable for large inputs.
Optimal solution for the given problem constraints.
Cons
Requires extra space for the hash map or array, although it's constant for this problem's constraints.
Slightly more complex to reason about and implement compared to the brute-force approach.
Solutions
Solution
class Solution { public int numEquivDominoPairs ( int [][] dominoes ) { int [] cnt = new int [ 100 ]; int ans = 0 ; for ( var e : dominoes ) { int x = e [ 0 ] < e [ 1 ] ? e [ 0 ] * 10 + e [ 1 ] : e [ 1 ] * 10 + e [ 0 ]; ans += cnt [ x ]++; } return ans ; } }class Solution { public: int numEquivDominoPairs ( vector < vector < int >>& dominoes ) { int cnt [ 100 ]{}; int ans = 0 ; for ( auto & e : dominoes ) { int x = e [ 0 ] < e [ 1 ] ? e [ 0 ] * 10 + e [ 1 ] : e [ 1 ] * 10 + e [ 0 ]; ans += cnt [ x ] ++ ; } return ans ; } };class Solution : def numEquivDominoPairs ( self , dominoes : List [ List [ int ]]) -> int : cnt = Counter () ans = 0 for a , b in dominoes : ans += cnt [( a , b )] cnt [( a , b )] += 1 if a != b : cnt [( b , a )] += 1 return 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.