1862. Sum of Floored Pairs
Problem Description
You are given an integer array nums
. You need to calculate the sum of floor(nums[i] / nums[j])
for all possible pairs of indices where 0 <= i, j < nums.length
.
The floor()
function returns the integer part of a division operation (rounds down to the nearest integer). For example, floor(7/3) = 2
and floor(5/2) = 2
.
You need to:
- Consider all possible pairs
(i, j)
from the array, including pairs wherei = j
- For each pair, calculate
floor(nums[i] / nums[j])
- Sum all these values together
- Return the final sum modulo
10^9 + 7
(since the answer could be very large)
For example, if nums = [2, 5, 9]
, you would calculate:
floor(2/2) + floor(2/5) + floor(2/9)
=1 + 0 + 0
floor(5/2) + floor(5/5) + floor(5/9)
=2 + 1 + 0
floor(9/2) + floor(9/5) + floor(9/9)
=4 + 1 + 1
- Total sum =
1 + 0 + 0 + 2 + 1 + 0 + 4 + 1 + 1 = 10
The challenge is to efficiently compute this sum, especially when the array is large, as a brute force approach checking all pairs would be too slow.
Intuition
Instead of computing floor(nums[i] / nums[j])
for every pair directly, let's think about this problem from a different angle. For a fixed denominator y
and a fixed quotient d
, we want to count how many numerators x
satisfy floor(x / y) = d
.
The key insight is that floor(x / y) = d
when d * y <= x < (d + 1) * y
. This means for a given denominator y
and quotient d
, the numerators that produce this quotient fall within the range [d * y, (d + 1) * y - 1]
.
Since we're dealing with counting elements in specific ranges, we can use a frequency count approach. First, count how many times each value appears in the array. Then, to efficiently query "how many elements fall in range [L, R]
", we can use prefix sums on the frequency array.
The algorithm becomes:
- Count the frequency of each element in
nums
- Build a prefix sum array where
s[i]
represents the count of elements with value<= i
- For each possible denominator value
y
that appears in the array:- For each possible quotient
d = 1, 2, 3, ...
:- Calculate the range of numerators:
[d * y, min(max_value, (d + 1) * y - 1)]
- Use the prefix sum to find how many numerators fall in this range
- Add
cnt[y] * d * (count of numerators in range)
to the answer - Stop when
d * y > max_value
since no numerators can be that large
- Calculate the range of numerators:
- For each possible quotient
This approach transforms the problem from O(n²) pair enumeration to O(max_value * log(max_value)) by cleverly grouping calculations by quotient values.
Learn more about Math, Binary Search and Prefix Sum patterns.
Solution Approach
Let's walk through the implementation step by step:
Step 1: Count Frequencies
cnt = Counter(nums)
mx = max(nums)
We use a Counter
to count the frequency of each element in nums
. We also find the maximum value mx
in the array, which helps us determine the bounds for our calculations.
Step 2: Build Prefix Sum Array
s = [0] * (mx + 1)
for i in range(1, mx + 1):
s[i] = s[i - 1] + cnt[i]
We create a prefix sum array s
where s[i]
represents the total count of elements with values <= i
. This allows us to query the count of elements in any range [L, R]
in O(1) time using s[R] - s[L-1]
.
Step 3: Enumerate Denominators and Quotients
ans = 0
for y in range(1, mx + 1):
if cnt[y]: # Only process if y exists in the array
d = 1
while d * y <= mx:
# Calculate contribution
d += 1
For each possible denominator value y
from 1 to mx
, we check if it actually exists in our array (cnt[y] > 0
). If it does, we enumerate all possible quotients d
.
Step 4: Calculate Contribution for Each (denominator, quotient) Pair
ans += cnt[y] * d * (s[min(mx, d * y + y - 1)] - s[d * y - 1])
ans %= mod
For a fixed denominator y
and quotient d
, the numerators that produce floor(x/y) = d
are in the range [d * y, (d + 1) * y - 1]
.
- The lower bound is
d * y
- The upper bound is
(d + 1) * y - 1
, but we cap it atmx
since no element exceedsmx
- Using the prefix sum array:
s[min(mx, d * y + y - 1)] - s[d * y - 1]
gives us the count of numerators in this range - We multiply by
cnt[y]
(frequency of denominator) andd
(the quotient value) to get the total contribution - We take modulo at each step to prevent overflow
The algorithm continues until d * y > mx
because beyond this point, there are no numerators large enough to produce a non-zero quotient.
Time Complexity: O(n + mx * log(mx)) where n is the length of the array and mx is the maximum value. The log factor comes from the fact that for each denominator y
, we iterate through approximately mx/y
quotients, and the sum mx/1 + mx/2 + ... + mx/mx
is O(mx * log(mx)).
Space Complexity: O(mx) for storing the frequency count and prefix sum arrays.
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 a small example with nums = [4, 3, 4, 5]
.
Step 1: Count Frequencies
cnt = {3: 1, 4: 2, 5: 1}
mx = 5
(maximum value)
Step 2: Build Prefix Sum Array
s[0] = 0 s[1] = 0 (no elements ≤ 1) s[2] = 0 (no elements ≤ 2) s[3] = 1 (one element ≤ 3, which is 3) s[4] = 3 (three elements ≤ 4: one 3 and two 4s) s[5] = 4 (all four elements ≤ 5)
Step 3: Process Each Denominator
For y = 3 (appears 1 time):
- d = 1: Range [3, 5], count = s[5] - s[2] = 4 - 0 = 4
- Contribution: 1 × 1 × 4 = 4
- (This means floor(3/3)=1, floor(4/3)=1, floor(4/3)=1, floor(5/3)=1)
- d = 2: Range [6, 8], but 6 > mx=5, so stop
For y = 4 (appears 2 times):
- d = 1: Range [4, 7], capped at [4, 5], count = s[5] - s[3] = 4 - 1 = 3
- Contribution: 2 × 1 × 3 = 6
- (Elements 4, 4, 5 give quotient 1 when divided by 4)
- d = 2: Range [8, 11], but 8 > mx=5, so stop
For y = 5 (appears 1 time):
- d = 1: Range [5, 9], capped at [5, 5], count = s[5] - s[4] = 4 - 3 = 1
- Contribution: 1 × 1 × 1 = 1
- (Only element 5 gives quotient 1 when divided by 5)
- d = 2: Range [10, 14], but 10 > mx=5, so stop
Total Sum = 4 + 6 + 1 = 11
Let's verify with brute force:
- floor(4/3)=1, floor(4/4)=1, floor(4/4)=1, floor(4/5)=0
- floor(3/3)=1, floor(3/4)=0, floor(3/4)=0, floor(3/5)=0
- floor(4/3)=1, floor(4/4)=1, floor(4/4)=1, floor(4/5)=0
- floor(5/3)=1, floor(5/4)=1, floor(5/4)=1, floor(5/5)=1
Sum = (1+1+1+0) + (1+0+0+0) + (1+1+1+0) + (1+1+1+1) = 3 + 1 + 3 + 4 = 11 ✓
Solution Implementation
1class Solution:
2 def sumOfFlooredPairs(self, nums: List[int]) -> int:
3 MOD = 10**9 + 7
4
5 # Count frequency of each number
6 frequency = Counter(nums)
7 max_value = max(nums)
8
9 # Build prefix sum array for cumulative counts
10 # prefix_sum[i] = total count of numbers from 1 to i
11 prefix_sum = [0] * (max_value + 1)
12 for i in range(1, max_value + 1):
13 prefix_sum[i] = prefix_sum[i - 1] + frequency[i]
14
15 result = 0
16
17 # For each unique value y in nums
18 for y in range(1, max_value + 1):
19 if frequency[y] == 0:
20 continue
21
22 # Calculate contribution when y is the divisor
23 # For each multiplier d, count how many x values give floor(x/y) = d
24 multiplier = 1
25 while multiplier * y <= max_value:
26 # Numbers in range [d*y, d*y + y - 1] give floor(x/y) = d
27 range_start = multiplier * y
28 range_end = min(max_value, multiplier * y + y - 1)
29
30 # Count of numbers in this range
31 count_in_range = prefix_sum[range_end] - prefix_sum[range_start - 1]
32
33 # Add contribution: frequency[y] * d * count_in_range
34 result += frequency[y] * multiplier * count_in_range
35 result %= MOD
36
37 multiplier += 1
38
39 return result
40
1class Solution {
2 public int sumOfFlooredPairs(int[] nums) {
3 final int MOD = 1_000_000_007;
4
5 // Find the maximum value in the array
6 int maxValue = 0;
7 for (int num : nums) {
8 maxValue = Math.max(maxValue, num);
9 }
10
11 // Count frequency of each number
12 int[] frequency = new int[maxValue + 1];
13 for (int num : nums) {
14 frequency[num]++;
15 }
16
17 // Build prefix sum array for quick range sum queries
18 int[] prefixSum = new int[maxValue + 1];
19 for (int i = 1; i <= maxValue; i++) {
20 prefixSum[i] = prefixSum[i - 1] + frequency[i];
21 }
22
23 long totalSum = 0;
24
25 // For each unique value as divisor
26 for (int divisor = 1; divisor <= maxValue; divisor++) {
27 if (frequency[divisor] > 0) {
28 // For each possible quotient value
29 for (int quotient = 1; quotient * divisor <= maxValue; quotient++) {
30 // Calculate range [quotient * divisor, (quotient + 1) * divisor - 1]
31 int rangeStart = quotient * divisor;
32 int rangeEnd = Math.min(maxValue, (quotient + 1) * divisor - 1);
33
34 // Count of numbers in this range that will give quotient when divided by divisor
35 int countInRange = prefixSum[rangeEnd] - prefixSum[rangeStart - 1];
36
37 // Add contribution: frequency[divisor] * quotient * countInRange
38 totalSum += (long) frequency[divisor] * quotient * countInRange;
39 totalSum %= MOD;
40 }
41 }
42 }
43
44 return (int) totalSum;
45 }
46}
47
1class Solution {
2public:
3 int sumOfFlooredPairs(vector<int>& nums) {
4 const int MOD = 1e9 + 7;
5
6 // Find the maximum value in the array
7 int maxValue = *max_element(nums.begin(), nums.end());
8
9 // Count frequency of each number
10 vector<int> frequency(maxValue + 1, 0);
11 for (int num : nums) {
12 frequency[num]++;
13 }
14
15 // Build prefix sum array for frequencies
16 vector<int> prefixSum(maxValue + 1, 0);
17 for (int i = 1; i <= maxValue; i++) {
18 prefixSum[i] = prefixSum[i - 1] + frequency[i];
19 }
20
21 long long result = 0;
22
23 // For each unique value y in the array
24 for (int y = 1; y <= maxValue; y++) {
25 if (frequency[y] == 0) {
26 continue;
27 }
28
29 // Calculate contribution of y as denominator
30 // For each quotient d, count how many x values give floor(x/y) = d
31 for (int quotient = 1; quotient * y <= maxValue; quotient++) {
32 // Range of x values that give floor(x/y) = quotient is [d*y, d*y + y - 1]
33 int rangeStart = quotient * y;
34 int rangeEnd = min(maxValue, quotient * y + y - 1);
35
36 // Count of numbers in range [rangeStart, rangeEnd]
37 int countInRange = prefixSum[rangeEnd] - prefixSum[rangeStart - 1];
38
39 // Add contribution: frequency[y] * quotient * countInRange
40 result += static_cast<long long>(frequency[y]) * quotient * countInRange;
41 result %= MOD;
42 }
43 }
44
45 return static_cast<int>(result);
46 }
47};
48
1/**
2 * Calculates the sum of floor(nums[i] / nums[j]) for all pairs (i, j) where 0 <= i, j < nums.length
3 * @param nums - Array of positive integers
4 * @returns The sum modulo 10^9 + 7
5 */
6function sumOfFlooredPairs(nums: number[]): number {
7 // Find the maximum value in the array
8 const maxValue: number = Math.max(...nums);
9
10 // Count frequency of each number (index represents the number, value represents its frequency)
11 const frequency: number[] = Array(maxValue + 1).fill(0);
12
13 // Prefix sum array to store cumulative frequencies
14 const prefixSum: number[] = Array(maxValue + 1).fill(0);
15
16 // Count the frequency of each number in nums
17 for (const num of nums) {
18 frequency[num]++;
19 }
20
21 // Build prefix sum array for quick range sum queries
22 // prefixSum[i] = total count of numbers from 0 to i
23 for (let i = 1; i <= maxValue; i++) {
24 prefixSum[i] = prefixSum[i - 1] + frequency[i];
25 }
26
27 let result: number = 0;
28 const MOD: number = 1e9 + 7;
29
30 // For each unique divisor y in the array
31 for (let divisor = 1; divisor <= maxValue; divisor++) {
32 // Skip if this divisor doesn't exist in the array
33 if (frequency[divisor] === 0) {
34 continue;
35 }
36
37 // For each possible quotient d, calculate contribution to the sum
38 // When dividing by divisor, numbers in range [d * divisor, (d + 1) * divisor - 1] give quotient d
39 for (let quotient = 1; quotient * divisor <= maxValue; quotient++) {
40 // Calculate the range of dividends that give this quotient when divided by divisor
41 const rangeStart: number = quotient * divisor;
42 const rangeEnd: number = Math.min((quotient + 1) * divisor - 1, maxValue);
43
44 // Count of numbers in the range [rangeStart, rangeEnd]
45 const countInRange: number = prefixSum[rangeEnd] - prefixSum[rangeStart - 1];
46
47 // Add contribution: frequency of divisor * quotient * count of numbers in range
48 result = (result + frequency[divisor] * quotient * countInRange) % MOD;
49 }
50 }
51
52 return result;
53}
54
Time and Space Complexity
Time Complexity: O(M × log M)
The algorithm consists of three main parts:
- Creating a Counter and finding the maximum value:
O(n)
where n is the length of nums - Building the prefix sum array:
O(M)
where M is the maximum value in nums - The nested loop calculation:
- The outer loop iterates through values from 1 to M:
O(M)
- For each value y with
cnt[y] > 0
, the inner while loop runs whiled * y <= M
- This means d can go up to
M/y
, so the inner loop runs approximatelyM/y
times - Summing across all y values:
Σ(M/y)
for y from 1 to M equalsM × (1/1 + 1/2 + 1/3 + ... + 1/M)
- This harmonic series sum is approximately
O(log M)
- Therefore, the total is
O(M × log M)
- The outer loop iterates through values from 1 to M:
Space Complexity: O(M)
The space usage comes from:
- Counter dictionary
cnt
: stores at most n unique values, but in worst case bounded byO(M)
- Prefix sum array
s
: has exactlyM + 1
elements, soO(M)
- Other variables use constant space
The dominant space usage is O(M)
from the prefix sum array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Without Proper Modulo Operations
Pitfall: One of the most common mistakes is forgetting to apply the modulo operation frequently enough, leading to integer overflow even in languages like Python that handle big integers.
# WRONG: Accumulating large values without modulo result += frequency[y] * multiplier * count_in_range # If this gets too large before applying modulo at the end, # it can cause memory issues or slow performance # CORRECT: Apply modulo after each addition result += frequency[y] * multiplier * count_in_range result %= MOD
2. Off-by-One Errors in Range Calculation
Pitfall: Incorrectly calculating the range of numerators that produce a specific quotient.
# WRONG: Forgetting the -1 in the upper bound
range_end = min(max_value, multiplier * y + y) # This includes one extra element
# CORRECT: The range for quotient d is [d*y, (d+1)*y - 1]
range_end = min(max_value, multiplier * y + y - 1)
3. Incorrect Prefix Sum Query
Pitfall: Using wrong indices when querying the prefix sum array for range counts.
# WRONG: Off-by-one in the lower bound count_in_range = prefix_sum[range_end] - prefix_sum[range_start] # CORRECT: Subtract prefix_sum[range_start - 1] to include range_start count_in_range = prefix_sum[range_end] - prefix_sum[range_start - 1]
4. Not Handling Edge Cases Properly
Pitfall: Failing to handle when range_start - 1
becomes negative (when multiplier = 1
and y = 1
).
# SAFE APPROACH: Ensure we don't access negative indices if range_start > 1: count_in_range = prefix_sum[range_end] - prefix_sum[range_start - 1] else: count_in_range = prefix_sum[range_end]
5. Inefficient Loop Termination
Pitfall: Using an inefficient condition for the while loop that might cause unnecessary iterations.
# INEFFICIENT: Checking a condition that's always false after a point while multiplier <= max_value: # This could run max_value times even when unnecessary # CORRECT: Stop when the lower bound of the range exceeds max_value while multiplier * y <= max_value: # More efficient termination
6. Memory Issues with Large Maximum Values
Pitfall: Creating arrays based on max(nums)
without considering memory constraints.
# POTENTIAL ISSUE: If max(nums) is very large (e.g., 10^5) prefix_sum = [0] * (max_value + 1) # Could use significant memory # SOLUTION: Consider using a dictionary-based approach if values are sparse # or compress the value space if there are gaps
7. Forgetting to Check Element Existence
Pitfall: Processing all values from 1 to max_value without checking if they actually exist in the array.
# WRONG: Processing non-existent denominators
for y in range(1, max_value + 1):
# Process y even if it's not in nums
# CORRECT: Skip if frequency is 0
for y in range(1, max_value + 1):
if frequency[y] == 0:
continue
Best Practice Solution: Always validate your range calculations with small examples, apply modulo operations consistently, and carefully handle array boundary conditions to avoid these common pitfalls.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!