Facebook Pixel

2001. Number of Pairs of Interchangeable Rectangles

MediumArrayHash TableMathCountingNumber Theory
Leetcode Link

Problem Description

You are given n rectangles, where each rectangle is defined by its width and height. The rectangles are stored in a 2D array called rectangles, where rectangles[i] = [width_i, height_i] represents the width and height of the i-th rectangle.

Two rectangles are considered interchangeable if they have the same width-to-height ratio. In other words, rectangles i and j (where i < j) are interchangeable if width_i/height_i == width_j/height_j using decimal division.

Your task is to count how many pairs of interchangeable rectangles exist in the given array.

For example:

  • A rectangle with width 4 and height 8 has a ratio of 4/8 = 0.5
  • A rectangle with width 2 and height 4 has a ratio of 2/4 = 0.5
  • These two rectangles would be interchangeable and form one pair

The solution uses the mathematical approach of reducing each ratio to its simplest form by dividing both width and height by their greatest common divisor (GCD). This allows for accurate comparison of ratios without floating-point precision issues. A hash table tracks rectangles with the same simplified ratio, and for each new rectangle with a matching ratio, the count of existing rectangles with that ratio gives the number of new pairs formed.

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

Intuition

The key insight is that comparing floating-point ratios directly can lead to precision errors. For instance, 1/3 and 2/6 should be equal, but floating-point division might give slightly different results due to rounding errors.

To avoid this issue, we need a way to represent each ratio uniquely and accurately. The mathematical approach is to reduce each fraction to its simplest form. For example, 4/8, 2/4, and 1/2 all represent the same ratio and should be reduced to 1/2.

We achieve this by finding the greatest common divisor (GCD) of the width and height, then dividing both by this GCD. After simplification, rectangles with the same ratio will have identical (width, height) pairs. For instance:

  • Rectangle [4, 8] → GCD = 4 → Simplified to (1, 2)
  • Rectangle [10, 20] → GCD = 10 → Simplified to (1, 2)
  • Rectangle [3, 6] → GCD = 3 → Simplified to (1, 2)

All three rectangles reduce to the same simplified form (1, 2), indicating they're interchangeable.

For counting pairs, when we encounter a rectangle with a ratio we've seen before, it can form pairs with all previously seen rectangles having the same ratio. If we've already seen k rectangles with a particular ratio, a new rectangle with that ratio forms k new pairs. This is why we maintain a counter: as we process each rectangle, we add the current count for its ratio to our answer, then increment the count for future rectangles.

Learn more about Math patterns.

Solution Approach

The implementation follows these steps:

  1. Initialize variables:

    • ans = 0 to store the total number of interchangeable pairs
    • cnt = Counter() - a hash table to track the count of each simplified ratio
  2. Process each rectangle: For each rectangle [w, h] in the input:

    a. Calculate the GCD: Find g = gcd(w, h) to determine the greatest common divisor of width and height

    b. Simplify the ratio: Divide both dimensions by the GCD to get the simplified form:

    • w = w // g
    • h = h // g

    c. Count pairs: Before adding the current rectangle, check how many rectangles with the same simplified ratio (w, h) we've already seen. Each of these can form a pair with the current rectangle, so add cnt[(w, h)] to our answer

    d. Update counter: Increment the count for this simplified ratio: cnt[(w, h)] += 1

  3. Return the result: After processing all rectangles, ans contains the total number of interchangeable pairs

Example walkthrough: Consider rectangles [[4,8], [3,6], [10,20], [15,30]]:

  • Rectangle [4,8]: GCD=4, simplified to (1,2), pairs=0, update cnt[(1,2)]=1
  • Rectangle [3,6]: GCD=3, simplified to (1,2), pairs=1 (forms 1 pair with first), update cnt[(1,2)]=2
  • Rectangle [10,20]: GCD=10, simplified to (1,2), pairs=2 (forms 2 pairs with first two), update cnt[(1,2)]=3
  • Rectangle [15,30]: GCD=15, simplified to (1,2), pairs=3 (forms 3 pairs with first three), update cnt[(1,2)]=4
  • Total pairs = 0 + 1 + 2 + 3 = 6

The time complexity is O(n × log(min(w, h))) where the log factor comes from the GCD calculation, and space complexity is O(n) for the hash 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 with rectangles [[4,8], [6,3], [2,4], [9,12]].

Step 1: Process rectangle [4,8]

  • Calculate GCD(4,8) = 4
  • Simplify: 4÷4 = 1, 8÷4 = 2 → simplified ratio is (1,2)
  • Check counter: cnt[(1,2)] = 0, so no pairs formed yet
  • Update counter: cnt[(1,2)] = 1
  • Running total pairs = 0

Step 2: Process rectangle [6,3]

  • Calculate GCD(6,3) = 3
  • Simplify: 6÷3 = 2, 3÷3 = 1 → simplified ratio is (2,1)
  • Check counter: cnt[(2,1)] = 0, so no pairs formed
  • Update counter: cnt[(2,1)] = 1
  • Running total pairs = 0

Step 3: Process rectangle [2,4]

  • Calculate GCD(2,4) = 2
  • Simplify: 2÷2 = 1, 4÷2 = 2 → simplified ratio is (1,2)
  • Check counter: cnt[(1,2)] = 1 (we've seen this ratio once before!)
  • This rectangle forms 1 pair with the first rectangle [4,8]
  • Update counter: cnt[(1,2)] = 2
  • Running total pairs = 0 + 1 = 1

Step 4: Process rectangle [9,12]

  • Calculate GCD(9,12) = 3
  • Simplify: 9÷3 = 3, 12÷3 = 4 → simplified ratio is (3,4)
  • Check counter: cnt[(3,4)] = 0, so no pairs formed
  • Update counter: cnt[(3,4)] = 1
  • Running total pairs = 1

Final Result: 1 interchangeable pair exists (rectangles [4,8] and [2,4])

The key insight: When we encounter a simplified ratio we've seen k times before, the current rectangle forms k new pairs with those previous rectangles.

Solution Implementation

1from typing import List
2from collections import Counter
3from math import gcd
4
5class Solution:
6    def interchangeableRectangles(self, rectangles: List[List[int]]) -> int:
7        # Initialize the count of interchangeable rectangle pairs
8        total_pairs = 0
9      
10        # Counter to store frequency of each normalized ratio
11        ratio_counter = Counter()
12      
13        # Process each rectangle
14        for width, height in rectangles:
15            # Find GCD to normalize the ratio to its simplest form
16            common_divisor = gcd(width, height)
17          
18            # Reduce width and height to their simplest ratio
19            normalized_width = width // common_divisor
20            normalized_height = height // common_divisor
21          
22            # Add the count of rectangles with the same ratio seen before
23            # (these can form pairs with the current rectangle)
24            total_pairs += ratio_counter[(normalized_width, normalized_height)]
25          
26            # Increment the counter for this ratio
27            ratio_counter[(normalized_width, normalized_height)] += 1
28      
29        return total_pairs
30
1class Solution {
2    public long interchangeableRectangles(int[][] rectangles) {
3        long count = 0;
4      
5        // Use a constant for encoding ratios into a single long value
6        int encodingMultiplier = rectangles.length + 1;
7      
8        // Map to store the frequency of each unique aspect ratio
9        // Key: encoded ratio (width * multiplier + height after reduction)
10        // Value: count of rectangles with this ratio
11        Map<Long, Integer> ratioFrequency = new HashMap<>();
12      
13        // Process each rectangle
14        for (int[] rectangle : rectangles) {
15            int width = rectangle[0];
16            int height = rectangle[1];
17          
18            // Reduce the width and height to their simplest ratio using GCD
19            int gcdValue = gcd(width, height);
20            width /= gcdValue;
21            height /= gcdValue;
22          
23            // Encode the reduced ratio into a single long value
24            // This ensures unique identification of each aspect ratio
25            long encodedRatio = (long) width * encodingMultiplier + height;
26          
27            // Count pairs: add the number of rectangles already seen with the same ratio
28            // These can be interchanged with the current rectangle
29            count += ratioFrequency.getOrDefault(encodedRatio, 0);
30          
31            // Update the frequency map for this ratio
32            ratioFrequency.merge(encodedRatio, 1, Integer::sum);
33        }
34      
35        return count;
36    }
37  
38    /**
39     * Calculate the Greatest Common Divisor using Euclidean algorithm
40     * @param a first number
41     * @param b second number
42     * @return the GCD of a and b
43     */
44    private int gcd(int a, int b) {
45        return b == 0 ? a : gcd(b, a % b);
46    }
47}
48
1class Solution {
2public:
3    long long interchangeableRectangles(vector<vector<int>>& rectangles) {
4        long long result = 0;
5        int totalRectangles = rectangles.size();
6      
7        // Map to store count of rectangles with same reduced aspect ratio
8        // Key: encoded aspect ratio, Value: count of rectangles
9        unordered_map<long long, int> aspectRatioCount;
10      
11        for (auto& rectangle : rectangles) {
12            int width = rectangle[0];
13            int height = rectangle[1];
14          
15            // Reduce the aspect ratio to its simplest form using GCD
16            int greatestCommonDivisor = gcd(width, height);
17            width /= greatestCommonDivisor;
18            height /= greatestCommonDivisor;
19          
20            // Create a unique hash for the reduced aspect ratio
21            // Using (totalRectangles + 1) as a separator to avoid collisions
22            // Formula: width * (n + 1) + height ensures unique encoding
23            long long aspectRatioHash = 1LL * width * (totalRectangles + 1) + height;
24          
25            // Add count of rectangles with same aspect ratio seen so far
26            // This forms pairs with the current rectangle
27            result += aspectRatioCount[aspectRatioHash];
28          
29            // Increment count for this aspect ratio
30            aspectRatioCount[aspectRatioHash]++;
31        }
32      
33        return result;
34    }
35};
36
1/**
2 * Counts the number of pairs of interchangeable rectangles.
3 * Two rectangles are interchangeable if they have the same width-to-height ratio.
4 * 
5 * @param rectangles - Array of rectangles where each rectangle is represented as [width, height]
6 * @returns The number of interchangeable rectangle pairs
7 */
8function interchangeableRectangles(rectangles: number[][]): number {
9    // Map to store the count of rectangles with the same normalized ratio
10    const ratioCount = new Map<number, number>();
11    let pairCount = 0;
12  
13    for (const [width, height] of rectangles) {
14        // Find GCD to normalize the ratio to its simplest form
15        const gcdValue = gcd(width, height);
16      
17        // Reduce the ratio to its simplest form
18        const normalizedWidth = Math.floor(width / gcdValue);
19        const normalizedHeight = Math.floor(height / gcdValue);
20      
21        // Create a unique key for the ratio using a hash function
22        // Multiplying width by a large number (array length + 1) ensures unique keys
23        const ratioKey = normalizedWidth * (rectangles.length + 1) + normalizedHeight;
24      
25        // Add the count of existing rectangles with the same ratio to the answer
26        // These form pairs with the current rectangle
27        pairCount += ratioCount.get(ratioKey) || 0;
28      
29        // Increment the count for this ratio
30        ratioCount.set(ratioKey, (ratioCount.get(ratioKey) || 0) + 1);
31    }
32  
33    return pairCount;
34}
35
36/**
37 * Calculates the Greatest Common Divisor (GCD) of two numbers using Euclidean algorithm.
38 * 
39 * @param a - First number
40 * @param b - Second number
41 * @returns The GCD of a and b
42 */
43function gcd(a: number, b: number): number {
44    if (b === 0) {
45        return a;
46    }
47    return gcd(b, a % b);
48}
49

Time and Space Complexity

Time Complexity: O(n × log M)

For each of the n rectangles, the algorithm performs:

  • Computing the GCD of width w and height h using the Euclidean algorithm, which takes O(log M) time where M is the maximum value between width and height
  • Integer division operations to normalize the rectangle dimensions: O(1)
  • Dictionary lookup and update operations: O(1) on average
  • Counter increment operation: O(1)

Since we iterate through all n rectangles and the GCD computation dominates with O(log M) time per rectangle, the overall time complexity is O(n × log M).

Space Complexity: O(n)

The space is used for:

  • The Counter dictionary cnt which stores normalized rectangle ratios as keys
  • In the worst case, all rectangles have unique normalized ratios (no interchangeable pairs)
  • This means the dictionary could store up to n unique entries

Therefore, the space complexity is O(n) where n is the number of rectangles.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Floating-Point Precision Issues

A common mistake is directly comparing floating-point ratios instead of using the GCD approach:

Incorrect approach:

def interchangeableRectangles(self, rectangles):
    total_pairs = 0
    ratio_counter = Counter()
  
    for width, height in rectangles:
        # WRONG: Using floating-point division
        ratio = width / height  
        total_pairs += ratio_counter[ratio]
        ratio_counter[ratio] += 1
  
    return total_pairs

Why it fails: Floating-point arithmetic can introduce precision errors. For example:

  • 4/14 might produce 0.2857142857142857
  • 2/7 might produce 0.2857142857142857 or 0.28571428571428575
  • These should be equal but might not match due to precision issues

Solution: Always use the GCD approach to reduce ratios to their simplest integer form (width//gcd, height//gcd).

2. Integer Division Confusion

Using integer division (//) to calculate the ratio directly:

Incorrect approach:

ratio = width // height  # WRONG: Loses precision

Why it fails: Integer division loses all decimal information. Rectangles [3,2] and [5,3] would both have ratio 1, but they're not actually interchangeable (1.5 ≠ 1.667).

3. Not Handling Edge Cases

Failing to consider special cases like:

  • Very large numbers that might cause overflow in some languages
  • Rectangles where width or height could be 1 (though the problem constraints typically prevent zero values)

Solution: The GCD approach naturally handles these cases by keeping numbers as small as possible through reduction.

4. Incorrect Pair Counting Logic

A subtle but critical mistake is counting pairs incorrectly:

Incorrect approach:

for width, height in rectangles:
    g = gcd(width, height)
    normalized = (width // g, height // g)
    ratio_counter[normalized] += 1
    total_pairs += ratio_counter[normalized] - 1  # WRONG ORDER

Why it fails: This increments the counter before calculating pairs, leading to off-by-one errors. You must add the current count (number of existing rectangles with this ratio) before incrementing.

Correct order:

  1. Add ratio_counter[normalized] to total (these are new pairs formed)
  2. Then increment ratio_counter[normalized] += 1
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

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

Load More