LeetCode 825 - Friends Of Appropriate Ages
The problem gives us an array ages, where each value represents the age of a person on a social media platform. We must determine how many friend requests are sent between people according to a specific set of rules.
Difficulty: 🟡 Medium
Topics: Array, Two Pointers, Binary Search, Sorting
Solution
Problem Understanding
The problem gives us an array ages, where each value represents the age of a person on a social media platform. We must determine how many friend requests are sent between people according to a specific set of rules.
A person x can send a friend request to person y only if all invalid conditions are false. The request is rejected if any of the following are true:
age[y] <= 0.5 * age[x] + 7age[y] > age[x]age[y] > 100 && age[x] < 100
If none of these conditions hold, then x sends a request to y.
The important detail is that friendship requests are directional. If person A can send a request to person B, that does not automatically mean B can send one back. We must count every valid directed request separately.
We are also told that a person cannot send a request to themselves.
The input size is at most 2 * 10^4, which is large enough that checking every pair directly with nested loops becomes expensive. Since the ages are limited to the range 1 to 120, this constraint strongly hints that counting or sorting based optimizations may be useful.
There are several important edge cases to keep in mind:
- Ages less than
15can never send requests because0.5 * age + 7becomes too restrictive. - Multiple people may have the same age, and they can often send requests to each other.
- Requests are directional, so valid pairs must be counted carefully.
- The
>100rule looks special, but it is actually already implied by theage[y] > age[x]condition in most cases. Still, it must be respected logically. - Duplicate ages can create many valid requests, so counting frequencies efficiently matters.
Approaches
Brute Force Approach
The most straightforward solution is to examine every ordered pair (x, y) where x != y.
For each pair, we directly evaluate the three conditions. If none are true, we increment the answer.
This approach is correct because it literally simulates the problem definition exactly. Every possible directed request is checked individually.
However, the time complexity is too high for larger inputs. With n = 20,000, checking every pair requires approximately 400 million comparisons in the worst case, which is inefficient.
Key Insight for an Optimal Solution
The important observation is that the validity of a request depends only on ages, not on person identities.
Since ages are bounded between 1 and 120, we can sort the array and use binary search or two pointers to efficiently count valid ranges.
For a fixed sender age a, valid recipients must satisfy:
recipient_age > 0.5 * a + 7recipient_age <= a
This forms a continuous valid interval after sorting.
Instead of checking every person individually, we can determine how many people fall into the valid range using binary search.
This reduces the complexity dramatically.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Checks every ordered pair directly |
| Optimal | O(n log n) | O(1) or O(log n) | Sorts ages and uses binary search to count valid ranges efficiently |
Algorithm Walkthrough
Optimal Sorting + Binary Search Algorithm
- Sort the
agesarray.
Sorting allows us to efficiently find contiguous valid age ranges for each person. Once sorted, all valid recipients for a sender appear in a continuous segment. 2. Iterate through each person as the sender.
Suppose the current sender age is age.
3. Compute the minimum valid recipient age.
The invalid condition is:
$age_y \leq 0.5 \cdot age_x + 7$
Therefore, valid recipients must satisfy:
$age_y > 0.5 \cdot age_x + 7$
We compute:
lower = 0.5 * age + 7
- Use binary search to find the first valid recipient.
Since the array is sorted, we can find the first index whose value is greater than lower.
5. Use binary search to find the last valid recipient.
Valid recipients must also satisfy:
$age_y \leq age_x$
We find the first index greater than age, which gives us the exclusive upper boundary.
6. Count how many people are in the valid interval.
If:
left = first valid index
right = first index greater than age
then:
valid_people = right - left
- Exclude the sender themselves.
Since a person cannot send a request to themselves, subtract one:
valid_people - 1
- Add the result to the total answer.
Repeat this process for every sender.
Why it works
After sorting, every valid recipient for a sender forms a contiguous range of ages. Binary search correctly finds the boundaries of this range. Because every sender is processed independently and self requests are excluded, every valid directed request is counted exactly once.
Python Solution
from bisect import bisect_right
from typing import List
class Solution:
def numFriendRequests(self, ages: List[int]) -> int:
ages.sort()
total_requests = 0
for age in ages:
if age < 15:
continue
lower_bound = 0.5 * age + 7
left = bisect_right(ages, lower_bound)
right = bisect_right(ages, age)
total_requests += right - left - 1
return total_requests
The implementation begins by sorting the ages array. This is essential because binary search only works on sorted data.
For each age, we first skip people younger than 15. Such people can never send valid requests because the lower bound condition becomes impossible to satisfy.
We then compute the minimum acceptable recipient age. Using bisect_right, we locate the first index strictly greater than that threshold.
Another binary search finds the first index greater than the sender's age. This defines the upper boundary of valid recipients.
The number of valid recipients is simply the size of this interval. We subtract one to exclude the sender themselves.
Finally, all valid request counts are accumulated into the result.
Go Solution
package main
import (
"sort"
)
func numFriendRequests(ages []int) int {
sort.Ints(ages)
totalRequests := 0
for _, age := range ages {
if age < 15 {
continue
}
lowerBound := float64(age)/2.0 + 7
left := sort.Search(len(ages), func(i int) bool {
return float64(ages[i]) > lowerBound
})
right := sort.Search(len(ages), func(i int) bool {
return ages[i] > age
})
totalRequests += right - left - 1
}
return totalRequests
}
The Go implementation follows the same logic as the Python solution.
Instead of Python's bisect_right, Go uses sort.Search with custom predicates. The first search locates the first valid recipient age greater than the lower bound, while the second search finds the first age strictly greater than the sender age.
Go slices are dynamic views over arrays, so no additional memory allocation is needed beyond sorting.
Integer overflow is not a concern because the maximum possible number of requests is well within Go's integer range.
Worked Examples
Example 1
ages = [16, 16]
After sorting:
[16, 16]
| Sender Age | Lower Bound | Left Index | Right Index | Valid Count | Requests Added |
|---|---|---|---|---|---|
| 16 | 15 | 0 | 2 | 2 | 1 |
| 16 | 15 | 0 | 2 | 2 | 1 |
Total requests:
1 + 1 = 2
Example 2
ages = [16, 17, 18]
Sorted array:
[16, 17, 18]
| Sender Age | Lower Bound | Valid Recipients | Requests Added |
|---|---|---|---|
| 16 | 15 | [16] | 0 |
| 17 | 15.5 | [16,17] | 1 |
| 18 | 16 | [17,18] | 1 |
Total:
2
The valid requests are:
17 -> 16
18 -> 17
Example 3
ages = [20,30,100,110,120]
Sorted array:
[20,30,100,110,120]
| Sender Age | Lower Bound | Valid Recipients | Requests Added |
|---|---|---|---|
| 20 | 17 | [20] | 0 |
| 30 | 22 | [30] | 0 |
| 100 | 57 | [100] | 0 |
| 110 | 62 | [100,110] | 1 |
| 120 | 67 | [100,110,120] | 2 |
Total:
3
Valid requests:
110 -> 100
120 -> 110
120 -> 100
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting dominates, each iteration performs binary searches |
| Space | O(1) or O(log n) | Depends on sorting implementation details |
The sorting step requires O(n log n) time. After sorting, each person performs two binary searches, each costing O(log n). Since this happens for all n people, the total remains O(n log n).
The algorithm uses only a few extra variables beyond the input array. Some sorting implementations use recursive stack space, which may contribute O(log n) auxiliary space.
Test Cases
from typing import List
class Solution:
def numFriendRequests(self, ages: List[int]) -> int:
from bisect import bisect_right
ages.sort()
total_requests = 0
for age in ages:
if age < 15:
continue
lower_bound = 0.5 * age + 7
left = bisect_right(ages, lower_bound)
right = bisect_right(ages, age)
total_requests += right - left - 1
return total_requests
solution = Solution()
assert solution.numFriendRequests([16, 16]) == 2 # identical valid ages
assert solution.numFriendRequests([16, 17, 18]) == 2 # directional requests
assert solution.numFriendRequests([20, 30, 100, 110, 120]) == 3 # mixed ages
assert solution.numFriendRequests([14, 14, 14]) == 0 # nobody under 15 can send requests
assert solution.numFriendRequests([15, 15]) == 2 # smallest valid sending age
assert solution.numFriendRequests([101, 102]) == 1 # older users can request slightly younger users
assert solution.numFriendRequests([120]) == 0 # single person
assert solution.numFriendRequests([16, 16, 16]) == 6 # all pairwise directed requests
assert solution.numFriendRequests([10, 20, 30, 40]) == 1 # only 40 -> 30 works
assert solution.numFriendRequests([99, 100, 101]) == 3 # requests across 100 boundary
| Test | Why |
|---|---|
[16,16] |
Basic mutual friendship case |
[16,17,18] |
Demonstrates directional behavior |
[20,30,100,110,120] |
Validates larger age gaps |
[14,14,14] |
Ensures under-15 users send no requests |
[15,15] |
Smallest age that can send valid requests |
[101,102] |
Tests ages above 100 |
[120] |
Single element edge case |
[16,16,16] |
Large duplicate group |
[10,20,30,40] |
Sparse valid relationships |
[99,100,101] |
Boundary behavior around age 100 |
Edge Cases
One important edge case is when all users are younger than 15. For example, [14, 14, 14]. A naive implementation might incorrectly count requests between equal ages. However, the condition age[y] <= 0.5 * age[x] + 7 prevents any valid requests from being sent. The implementation handles this efficiently by immediately skipping ages below 15.
Another important case involves many duplicate ages, such as [16, 16, 16]. Every person can send requests to every other person except themselves. Since there are three people, each sends requests to two others, producing 6 total requests. Incorrect handling of duplicates or self counting could easily produce wrong answers. The binary search interval combined with subtracting one correctly handles this situation.
A third edge case occurs around the age 100 boundary. For example, [99, 100, 101]. The special condition about ages above 100 can be confusing. The implementation naturally handles this because recipients must already satisfy recipient_age <= sender_age. Thus, 101 may send to 100, but 99 cannot send to 101. The sorted range logic enforces these rules automatically.