Facebook Pixel

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:

  1. Consider all possible pairs (i, j) from the array, including pairs where i = j
  2. For each pair, calculate floor(nums[i] / nums[j])
  3. Sum all these values together
  4. 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.

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

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:

  1. Count the frequency of each element in nums
  2. Build a prefix sum array where s[i] represents the count of elements with value <= i
  3. 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

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 at mx since no element exceeds mx
  • 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) and d (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 Evaluator

Example 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:

  1. Creating a Counter and finding the maximum value: O(n) where n is the length of nums
  2. Building the prefix sum array: O(M) where M is the maximum value in nums
  3. 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 while d * y <= M
    • This means d can go up to M/y, so the inner loop runs approximately M/y times
    • Summing across all y values: Σ(M/y) for y from 1 to M equals M × (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)

Space Complexity: O(M)

The space usage comes from:

  • Counter dictionary cnt: stores at most n unique values, but in worst case bounded by O(M)
  • Prefix sum array s: has exactly M + 1 elements, so O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More