2748. Number of Beautiful Pairs
Problem Description
You are given a 0-indexed integer array nums
. Your task is to count the number of "beautiful pairs" in this array.
A pair of indices (i, j)
where 0 <= i < j < nums.length
is considered beautiful if:
- The first digit of
nums[i]
and the last digit ofnums[j]
are coprime
Two integers x
and y
are coprime if their greatest common divisor (GCD) equals 1. In other words, they share no common divisors other than 1.
For example:
- If
nums[i] = 234
, the first digit is2
- If
nums[j] = 567
, the last digit is7
- Since
gcd(2, 7) = 1
, this would be a beautiful pair
The problem asks you to return the total count of all beautiful pairs in the array.
Key Points:
- You need to check pairs where
i < j
(earlier index paired with later index) - Extract the first digit of
nums[i]
and last digit ofnums[j]
- Check if these two digits are coprime (their GCD is 1)
- Count all such valid pairs
Intuition
The naive approach would be to check every pair (i, j)
where i < j
, extract the first digit of nums[i]
and last digit of nums[j]
, then check if they're coprime. This would take O(n²)
time.
However, we can optimize this by observing a key insight: the first digit of any number can only be one of 9 values (1-9), and the last digit can only be one of 10 values (0-9). This limited range allows us to use counting.
The clever idea is to process the array from left to right. For each number at position j
, we want to count how many previous numbers (at positions i < j
) have a first digit that is coprime with the current number's last digit.
Since we only care about first digits of previous numbers, we can maintain a frequency array cnt[10]
where cnt[d]
stores how many numbers we've seen so far that have d
as their first digit.
For each number x
in the array:
- We check its last digit (
x % 10
) - For each possible first digit value
y
(0-9), if we've seen numbers with that first digit before (cnt[y] > 0
) andgcd(x % 10, y) == 1
, then we've foundcnt[y]
beautiful pairs - After processing
x
as the second element of potential pairs, we add it to our count by incrementingcnt[first_digit_of_x]
This way, we process each element once and for each element we only check 10 possible digit values, giving us O(n)
time complexity instead of O(n²)
.
Learn more about Math patterns.
Solution Approach
We implement the counting approach using an array cnt
of size 10 to track the frequency of first digits from previously processed numbers.
Step-by-step implementation:
-
Initialize the counter array: Create
cnt = [0] * 10
to store counts of first digits (indices 0-9). -
Initialize answer: Set
ans = 0
to accumulate the count of beautiful pairs. -
Process each number: For each number
x
innums
:a. Find beautiful pairs with
x
as the second element:- Extract the last digit of
x
usingx % 10
- Iterate through all possible first digits
y
from 0 to 9 - If
cnt[y] > 0
(we've seen numbers with first digity
) andgcd(x % 10, y) == 1
(the digits are coprime), addcnt[y]
to the answer
b. Update the counter for future pairs:
- Extract the first digit of
x
by converting to string:int(str(x)[0])
- Increment
cnt[first_digit]
by 1
- Extract the last digit of
-
Return the result: After processing all numbers, return
ans
.
Example walkthrough:
If nums = [25, 37, 14]
:
- Process
25
: No previous numbers, so no pairs. Updatecnt[2] = 1
- Process
37
: Last digit is 7. Check if 7 is coprime with first digit 2 (from 25). Sincegcd(7, 2) = 1
, we found 1 pair. Updatecnt[3] = 1
- Process
14
: Last digit is 4. Check coprimality with first digits 2 and 3.gcd(4, 2) = 2 ≠ 1
(not coprime),gcd(4, 3) = 1
(coprime), so we found 1 more pair. Updatecnt[1] = 1
- Total: 2 beautiful pairs
The algorithm efficiently counts beautiful pairs in O(n)
time with O(1)
space (since the cnt
array has fixed size 10).
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [11, 21, 12]
:
Initial Setup:
cnt = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
(tracks frequency of first digits)ans = 0
(counts beautiful pairs)
Step 1: Process nums[0] = 11
- Last digit:
11 % 10 = 1
- Check all possible first digits (0-9) in
cnt
:- All counts are 0, so no pairs can be formed yet
- First digit of 11:
int(str(11)[0]) = 1
- Update:
cnt[1] = 1
- Current state:
cnt = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
,ans = 0
Step 2: Process nums[1] = 21
- Last digit:
21 % 10 = 1
- Check all possible first digits (0-9) in
cnt
:cnt[1] = 1
(we have one number starting with 1)- Check if
gcd(1, 1) = 1
? Yes! So we addcnt[1] = 1
to answer ans = 0 + 1 = 1
- First digit of 21:
int(str(21)[0]) = 2
- Update:
cnt[2] = 1
- Current state:
cnt = [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]
,ans = 1
Step 3: Process nums[2] = 12
- Last digit:
12 % 10 = 2
- Check all possible first digits (0-9) in
cnt
:cnt[1] = 1
: Checkgcd(2, 1) = 1
? Yes! Add 1 to answercnt[2] = 1
: Checkgcd(2, 2) = 2
? No, not coprimeans = 1 + 1 = 2
- First digit of 12:
int(str(12)[0]) = 1
- Update:
cnt[1] = 2
- Final state:
cnt = [0, 2, 1, 0, 0, 0, 0, 0, 0, 0]
,ans = 2
Result: We found 2 beautiful pairs:
- Pair (0, 1): nums[0]=11 (first digit 1) and nums[1]=21 (last digit 1),
gcd(1,1)=1
✓ - Pair (0, 2): nums[0]=11 (first digit 1) and nums[2]=12 (last digit 2),
gcd(1,2)=1
✓
Solution Implementation
1from typing import List
2from math import gcd
3
4class Solution:
5 def countBeautifulPairs(self, nums: List[int]) -> int:
6 # Track count of first digits seen so far
7 first_digit_count = [0] * 10
8 beautiful_pairs_count = 0
9
10 # Process each number in the array
11 for current_num in nums:
12 # Get the last digit of current number
13 last_digit = current_num % 10
14
15 # Check against all possible first digits (0-9)
16 for first_digit in range(10):
17 # If we've seen numbers with this first digit before
18 # and it forms a coprime pair with current number's last digit
19 if first_digit_count[first_digit] > 0 and gcd(last_digit, first_digit) == 1:
20 # Add the count of numbers with this first digit
21 beautiful_pairs_count += first_digit_count[first_digit]
22
23 # Get the first digit of current number and update its count
24 # Convert to string to easily access first character
25 current_first_digit = int(str(current_num)[0])
26 first_digit_count[current_first_digit] += 1
27
28 return beautiful_pairs_count
29
1class Solution {
2 /**
3 * Counts the number of beautiful pairs in the array.
4 * A beautiful pair (i, j) where i < j exists when gcd(firstDigit(nums[i]), lastDigit(nums[j])) == 1
5 *
6 * @param nums the input array of positive integers
7 * @return the count of beautiful pairs
8 */
9 public int countBeautifulPairs(int[] nums) {
10 // Array to store count of first digits (0-9) seen so far
11 int[] firstDigitCount = new int[10];
12 int beautifulPairsCount = 0;
13
14 // Iterate through each number in the array
15 for (int currentNumber : nums) {
16 // Check current number's last digit against all previous first digits
17 int lastDigit = currentNumber % 10;
18
19 for (int firstDigit = 0; firstDigit < 10; ++firstDigit) {
20 // If we've seen this first digit before and it's coprime with current last digit
21 if (firstDigitCount[firstDigit] > 0 && gcd(lastDigit, firstDigit) == 1) {
22 // Add the count of numbers with this first digit
23 beautifulPairsCount += firstDigitCount[firstDigit];
24 }
25 }
26
27 // Extract the first digit of current number
28 int tempNumber = currentNumber;
29 while (tempNumber > 9) {
30 tempNumber /= 10;
31 }
32 // Increment count for this first digit
33 ++firstDigitCount[tempNumber];
34 }
35
36 return beautifulPairsCount;
37 }
38
39 /**
40 * Calculates the greatest common divisor of two numbers using Euclidean algorithm
41 *
42 * @param a first number
43 * @param b second number
44 * @return the GCD of a and b
45 */
46 private int gcd(int a, int b) {
47 return b == 0 ? a : gcd(b, a % b);
48 }
49}
50
1class Solution {
2public:
3 int countBeautifulPairs(vector<int>& nums) {
4 // Array to count occurrences of first digits (0-9)
5 int firstDigitCount[10]{};
6 int beautifulPairsCount = 0;
7
8 // Iterate through each number in the array
9 for (int currentNum : nums) {
10 // Check all possible first digits (0-9) from previous numbers
11 for (int firstDigit = 0; firstDigit < 10; ++firstDigit) {
12 // If there are numbers with this first digit and
13 // GCD of current number's last digit with this first digit is 1
14 if (firstDigitCount[firstDigit] && gcd(currentNum % 10, firstDigit) == 1) {
15 // Add the count of numbers with this first digit to result
16 beautifulPairsCount += firstDigitCount[firstDigit];
17 }
18 }
19
20 // Extract the first digit of current number
21 int tempNum = currentNum;
22 while (tempNum > 9) {
23 tempNum /= 10;
24 }
25
26 // Increment count for this first digit
27 ++firstDigitCount[tempNum];
28 }
29
30 return beautifulPairsCount;
31 }
32};
33
1/**
2 * Counts the number of beautiful pairs in the array.
3 * A beautiful pair (i, j) where i < j exists when gcd(first digit of nums[i], last digit of nums[j]) = 1
4 * @param nums - Array of positive integers
5 * @returns Number of beautiful pairs
6 */
7function countBeautifulPairs(nums: number[]): number {
8 // Array to count occurrences of first digits (0-9)
9 const firstDigitCount: number[] = Array(10).fill(0);
10 let beautifulPairsCount: number = 0;
11
12 // Process each number in the array
13 for (const currentNumber of nums) {
14 // Check current number's last digit against all previously seen first digits
15 const lastDigit: number = currentNumber % 10;
16
17 for (let firstDigit: number = 0; firstDigit < 10; firstDigit++) {
18 // If we've seen this first digit before and it forms a coprime pair with current last digit
19 if (firstDigitCount[firstDigit] > 0 && gcd(lastDigit, firstDigit) === 1) {
20 beautifulPairsCount += firstDigitCount[firstDigit];
21 }
22 }
23
24 // Extract the first digit of current number
25 let tempNumber: number = currentNumber;
26 while (tempNumber > 9) {
27 tempNumber = Math.floor(tempNumber / 10);
28 }
29
30 // Increment count for this first digit
31 firstDigitCount[tempNumber]++;
32 }
33
34 return beautifulPairsCount;
35}
36
37/**
38 * Calculates the greatest common divisor of two numbers using Euclidean algorithm
39 * @param a - First number
40 * @param b - Second number
41 * @returns Greatest common divisor of a and b
42 */
43function gcd(a: number, b: number): number {
44 // Base case: if b is 0, gcd is a
45 if (b === 0) {
46 return a;
47 }
48 // Recursive case: gcd(a, b) = gcd(b, a mod b)
49 return gcd(b, a % b);
50}
51
Time and Space Complexity
Time Complexity: O(n × k)
where n
is the length of the array nums
and k = 10
(constant representing possible digits 0-9).
- The outer loop iterates through all
n
elements innums
- For each element, the inner loop iterates through 10 possible digits (0-9)
- Inside the inner loop,
gcd(x % 10, y)
operation takesO(log min(x % 10, y))
time, but since both values are single digits (0-9), this is effectivelyO(1)
- Converting
x
to string to get the first digitstr(x)[0]
takesO(log M)
time whereM
is the maximum value innums
- Overall:
O(n × (10 + log M))
=O(n × (k + log M))
Space Complexity: O(k)
where k = 10
(constant).
- The
cnt
array has a fixed size of 10 to store counts of first digits - Converting integer
x
to string creates temporary space ofO(log M)
whereM
is the maximum value - Overall:
O(10 + log M)
=O(k + log M)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Order of Operations
Issue: A common mistake is updating the first_digit_count
array before checking for beautiful pairs with the current number. This would incorrectly count a number pairing with itself.
Incorrect Implementation:
for current_num in nums:
# WRONG: Updating count first
current_first_digit = int(str(current_num)[0])
first_digit_count[current_first_digit] += 1
# Then checking pairs - this includes self-pairing!
last_digit = current_num % 10
for first_digit in range(10):
if first_digit_count[first_digit] > 0 and gcd(last_digit, first_digit) == 1:
beautiful_pairs_count += first_digit_count[first_digit]
Solution: Always check for pairs first, then update the counter. This ensures we only consider pairs where i < j
.
Pitfall 2: Edge Case with GCD of Zero
Issue: When checking gcd(last_digit, first_digit)
, if first_digit
is 0, the GCD calculation might behave unexpectedly. While gcd(n, 0) = n
mathematically, this could lead to incorrect results if not handled properly.
Solution: Either handle zero explicitly or rely on Python's math.gcd
which correctly handles gcd(n, 0) = n
. Since we need gcd = 1
for coprime numbers, and gcd(n, 0) = n
which is only 1 when n = 1
, the logic works correctly.
Pitfall 3: Extracting First Digit of Single-Digit Numbers
Issue: For single-digit numbers, both first and last digits are the same. The string conversion str(current_num)[0]
works correctly, but developers might overthink this case.
Example: For nums = [7]
, the first digit is 7 and last digit is 7. No pairs exist since we need at least two elements.
Solution: The current implementation handles this naturally - no special case needed.
Pitfall 4: Misunderstanding Pair Direction
Issue: Confusing which number provides the first digit and which provides the last digit. The problem states: first digit of nums[i]
and last digit of nums[j]
where i < j
.
Incorrect Understanding: Checking first digit of nums[j]
against last digit of nums[i]
.
Solution: Remember the pattern - as we iterate through the array, for each current number (acting as nums[j]
), we use its last digit and check against first digits of all previous numbers (acting as nums[i]
).
How does merge sort divide the problem into subproblems?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!