2426. Number of Pairs Satisfying Inequality
Problem Description
You are given two integer arrays nums1
and nums2
, both of size n
, and an integer diff
. Your task is to find the number of pairs (i, j)
that satisfy both of these conditions:
0 <= i < j <= n - 1
(meaningi
comes beforej
in the arrays)nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff
The second condition can be understood as: the difference between elements at positions i
and j
in nums1
should not exceed the difference between elements at the same positions in nums2
plus the given diff
value.
For example, if you have:
nums1 = [3, 5]
nums2 = [1, 2]
diff = 1
You would check the pair (0, 1)
:
- Left side:
nums1[0] - nums1[1] = 3 - 5 = -2
- Right side:
nums2[0] - nums2[1] + diff = 1 - 2 + 1 = 0
- Since
-2 <= 0
, this pair satisfies the condition
The problem asks you to count all such valid pairs and return the total count.
Intuition
The key insight is to rearrange the given inequality to make it easier to work with. Starting with nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff
, we can rearrange the terms by grouping elements from the same index together:
nums1[i] - nums2[i] <= nums1[j] - nums2[j] + diff
This transformation is crucial because now we can define a new array where each element is nums[k] = nums1[k] - nums2[k]
. The problem then simplifies to finding pairs (i, j)
where i < j
and nums[i] <= nums[j] + diff
.
Now we need to count how many valid pairs exist. For each position j
, we want to count how many positions i
before it (where i < j
) satisfy nums[i] <= nums[j] + diff
. This is essentially asking: "For each element, how many elements before it are at most nums[j] + diff
?"
This type of query - counting elements less than or equal to a certain value in a dynamic set - is a classic use case for data structures like Binary Indexed Tree (Fenwick Tree) or Segment Tree. As we iterate through the array from left to right:
- For each position
j
, we query how many elements we've seen so far are<= nums[j] + diff
- Then we add
nums[j]
to our data structure for future queries
The Binary Indexed Tree is particularly efficient here because it supports both point updates and range queries in O(log n)
time. Since the values might be negative or very large, we apply an offset (like 40000) to ensure all values are positive and within a reasonable range for the tree implementation.
Learn more about Segment Tree, Binary Search, Divide and Conquer and Merge Sort patterns.
Solution Approach
The solution implements the intuition using a Binary Indexed Tree (Fenwick Tree) to efficiently count valid pairs.
Step 1: Transform the Problem
First, we create a transformed view of the problem. For each index, we calculate v = nums1[i] - nums2[i]
. This transforms our inequality into finding pairs where v[i] <= v[j] + diff
with i < j
.
Step 2: Binary Indexed Tree Setup
The BinaryIndexedTree
class is initialized with a size of 10^5
to handle the range of possible values. The tree supports:
update(x, delta)
: Addsdelta
to the count at positionx
query(x)
: Returns the sum of all elements from index 1 tox
lowbit(x)
: Helper function that returnsx & -x
, which isolates the rightmost set bit
Step 3: Process Elements and Count Pairs
The main algorithm iterates through both arrays simultaneously:
for a, b in zip(nums1, nums2):
v = a - b # Calculate the difference
ans += tree.query(v + diff + 40000) # Count valid pairs
tree.update(v + 40000, 1) # Add current element to tree
For each position j
:
- Calculate
v = nums1[j] - nums2[j]
- Query the tree for count of all elements
<= v + diff
. The offset of 40000 is added to handle negative values - Add this count to our answer (these are all valid pairs with current position as
j
) - Update the tree by adding the current
v
value (with offset) for future queries
Why the Offset of 40000?
Since the Binary Indexed Tree uses 1-based indexing and needs positive indices, we add 40000 to shift all possible values into a positive range. This ensures that even if v
is negative (as low as around -40000), the index v + 40000
remains positive.
Time Complexity: O(n log n)
where n
is the length of the arrays. Each query and update operation takes O(log n)
time, and we perform n
such operations.
Space Complexity: O(10^5)
for the Binary Indexed Tree array, which is constant space relative to the input size.
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 concrete example to understand how the solution works:
Given:
nums1 = [3, 2, 5]
nums2 = [2, 2, 1]
diff = 1
Step 1: Transform the arrays
First, we calculate v[i] = nums1[i] - nums2[i]
for each index:
v[0] = 3 - 2 = 1
v[1] = 2 - 2 = 0
v[2] = 5 - 1 = 4
So our transformed array is v = [1, 0, 4]
.
Step 2: Process each element with Binary Indexed Tree
We'll use a Binary Indexed Tree (BIT) to count valid pairs. For simplicity in this example, let's use a smaller offset of 10 instead of 40000.
Iteration 1 (i=0, v=1):
- Query: How many elements in BIT are ≤
v + diff + offset = 1 + 1 + 10 = 12
?- BIT is empty, so count = 0
- Update: Add
v + offset = 1 + 10 = 11
to BIT - Running answer: 0
Iteration 2 (i=1, v=0):
- Query: How many elements in BIT are ≤
v + diff + offset = 0 + 1 + 10 = 11
?- BIT contains one element at index 11, so count = 1
- This represents the valid pair (0, 1) where
v[0]=1 ≤ v[1]+diff=0+1=1
✓
- Update: Add
v + offset = 0 + 10 = 10
to BIT - Running answer: 0 + 1 = 1
Iteration 3 (i=2, v=4):
- Query: How many elements in BIT are ≤
v + diff + offset = 4 + 1 + 10 = 15
?- BIT contains elements at indices 10 and 11, so count = 2
- This represents valid pairs:
- (0, 2):
v[0]=1 ≤ v[2]+diff=4+1=5
✓ - (1, 2):
v[1]=0 ≤ v[2]+diff=4+1=5
✓
- (0, 2):
- Update: Add
v + offset = 4 + 10 = 14
to BIT - Running answer: 1 + 2 = 3
Result: The total number of valid pairs is 3.
Verification: Let's verify our three pairs using the original condition:
- Pair (0,1):
nums1[0]-nums1[1] = 3-2 = 1
≤nums2[0]-nums2[1]+diff = 2-2+1 = 1
✓ - Pair (0,2):
nums1[0]-nums1[2] = 3-5 = -2
≤nums2[0]-nums2[2]+diff = 2-1+1 = 2
✓ - Pair (1,2):
nums1[1]-nums1[2] = 2-5 = -3
≤nums2[1]-nums2[2]+diff = 2-1+1 = 2
✓
All three pairs satisfy the condition, confirming our answer of 3.
Solution Implementation
1from typing import List
2
3
4class BinaryIndexedTree:
5 """Binary Indexed Tree (Fenwick Tree) for efficient prefix sum queries and updates."""
6
7 def __init__(self, n: int) -> None:
8 """
9 Initialize the Binary Indexed Tree.
10
11 Args:
12 n: The size of the tree (1-indexed, so actual array size is n+1)
13 """
14 self.n = n
15 self.c = [0] * (n + 1) # Tree array, 1-indexed
16
17 @staticmethod
18 def lowbit(x: int) -> int:
19 """
20 Get the lowest set bit of x.
21
22 Args:
23 x: Input integer
24
25 Returns:
26 The value of the lowest set bit
27 """
28 return x & -x
29
30 def update(self, x: int, delta: int) -> None:
31 """
32 Update the value at index x by adding delta.
33
34 Args:
35 x: The index to update (1-indexed)
36 delta: The value to add
37 """
38 while x <= self.n:
39 self.c[x] += delta
40 x += BinaryIndexedTree.lowbit(x)
41
42 def query(self, x: int) -> int:
43 """
44 Query the prefix sum from index 1 to x.
45
46 Args:
47 x: The end index of the range (1-indexed)
48
49 Returns:
50 The sum of elements from index 1 to x
51 """
52 sum_value = 0
53 while x > 0:
54 sum_value += self.c[x]
55 x -= BinaryIndexedTree.lowbit(x)
56 return sum_value
57
58
59class Solution:
60 def numberOfPairs(self, nums1: List[int], nums2: List[int], diff: int) -> int:
61 """
62 Count the number of pairs (i, j) where i < j and
63 nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff.
64
65 This can be rewritten as: (nums1[i] - nums2[i]) <= (nums1[j] - nums2[j]) + diff
66
67 Args:
68 nums1: First array of integers
69 nums2: Second array of integers
70 diff: The difference threshold
71
72 Returns:
73 The count of valid pairs
74 """
75 # Create a BIT with size 10^5 to handle the range of possible values
76 tree = BinaryIndexedTree(100000)
77
78 # Initialize answer counter
79 answer = 0
80
81 # Process each pair of elements from nums1 and nums2
82 for a, b in zip(nums1, nums2):
83 # Calculate the difference value for current position
84 value = a - b
85
86 # Query how many previous values are <= value + diff
87 # Add 40000 as offset to handle negative values (map to positive range)
88 answer += tree.query(value + diff + 40000)
89
90 # Update the tree with current value (also with offset)
91 tree.update(value + 40000, 1)
92
93 return answer
94
1/**
2 * Binary Indexed Tree (Fenwick Tree) implementation for efficient range sum queries
3 * and point updates in O(log n) time complexity
4 */
5class BinaryIndexedTree {
6 private int n; // Size of the tree
7 private int[] c; // Tree array (1-indexed)
8
9 /**
10 * Constructor to initialize the Binary Indexed Tree
11 * @param n Size of the tree
12 */
13 public BinaryIndexedTree(int n) {
14 this.n = n;
15 this.c = new int[n + 1]; // 1-indexed array
16 }
17
18 /**
19 * Calculate the lowest set bit of x
20 * This determines the range of responsibility for each node
21 * @param x Input number
22 * @return The value of the lowest set bit
23 */
24 public static final int lowbit(int x) {
25 return x & -x;
26 }
27
28 /**
29 * Update the value at position x by adding delta
30 * Propagates the change to all affected nodes in the tree
31 * @param x Position to update (1-indexed)
32 * @param delta Value to add
33 */
34 public void update(int x, int delta) {
35 while (x <= n) {
36 c[x] += delta;
37 x += lowbit(x); // Move to next responsible node
38 }
39 }
40
41 /**
42 * Query the prefix sum from index 1 to x
43 * @param x Upper bound of the range (1-indexed)
44 * @return Sum of elements from index 1 to x
45 */
46 public int query(int x) {
47 int sum = 0;
48 while (x > 0) {
49 sum += c[x];
50 x -= lowbit(x); // Move to previous range
51 }
52 return sum;
53 }
54}
55
56/**
57 * Solution class for counting pairs that satisfy the given condition
58 */
59class Solution {
60 /**
61 * Count the number of pairs (i, j) where i < j and
62 * nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff
63 *
64 * Rearranging: nums1[i] - nums2[i] <= nums1[j] - nums2[j] + diff
65 *
66 * @param nums1 First array
67 * @param nums2 Second array
68 * @param diff The difference threshold
69 * @return Number of valid pairs
70 */
71 public long numberOfPairs(int[] nums1, int[] nums2, int diff) {
72 // Initialize BIT with size 100000 to handle the range of possible values
73 BinaryIndexedTree tree = new BinaryIndexedTree(100000);
74 long answer = 0;
75
76 // Process each element
77 for (int i = 0; i < nums1.length; ++i) {
78 // Calculate the difference for current position
79 int value = nums1[i] - nums2[i];
80
81 // Count how many previous elements satisfy the condition
82 // Add 40000 offset to handle negative values (shift to positive range)
83 answer += tree.query(value + diff + 40000);
84
85 // Add current value to the tree for future queries
86 tree.update(value + 40000, 1);
87 }
88
89 return answer;
90 }
91}
92
1class BinaryIndexedTree {
2public:
3 int size; // Size of the tree (number of elements)
4 vector<int> tree; // The BIT array (1-indexed)
5
6 // Constructor: Initialize BIT with given size
7 BinaryIndexedTree(int _size)
8 : size(_size)
9 , tree(_size + 1) {}
10
11 // Update operation: Add delta to position x
12 // Time complexity: O(log n)
13 void update(int x, int delta) {
14 // Traverse all ancestors of node x
15 while (x <= size) {
16 tree[x] += delta;
17 x += lowbit(x); // Move to next node that needs updating
18 }
19 }
20
21 // Query operation: Get prefix sum from index 1 to x
22 // Time complexity: O(log n)
23 int query(int x) {
24 int sum = 0;
25 // Traverse all nodes that contribute to the sum
26 while (x > 0) {
27 sum += tree[x];
28 x -= lowbit(x); // Move to parent node
29 }
30 return sum;
31 }
32
33 // Helper function: Get the lowest set bit of x
34 // This determines the range of responsibility for each node
35 int lowbit(int x) {
36 return x & -x;
37 }
38};
39
40class Solution {
41public:
42 // Count pairs (i, j) where i < j and nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff
43 // Rearranging: nums1[i] - nums2[i] <= nums1[j] - nums2[j] + diff
44 long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int diff) {
45 // Create BIT with size 100000 to handle range of possible values
46 BinaryIndexedTree* tree = new BinaryIndexedTree(1e5);
47
48 long long pairCount = 0;
49
50 // Process each element
51 for (int i = 0; i < nums1.size(); ++i) {
52 // Calculate difference for current position
53 int currentDiff = nums1[i] - nums2[i];
54
55 // Count how many previous elements satisfy the condition
56 // We need to count elements with value <= currentDiff + diff
57 // Add 40000 offset to handle negative values (shift to positive range)
58 pairCount += tree->query(currentDiff + diff + 40000);
59
60 // Add current difference to the tree for future queries
61 tree->update(currentDiff + 40000, 1);
62 }
63
64 return pairCount;
65 }
66};
67
1// Binary Indexed Tree (Fenwick Tree) for efficient prefix sum queries and updates
2let size: number; // Size of the tree (number of elements)
3let tree: number[]; // The BIT array (1-indexed)
4
5// Initialize BIT with given size
6function initializeBIT(_size: number): void {
7 size = _size;
8 tree = new Array(_size + 1).fill(0);
9}
10
11// Helper function: Get the lowest set bit of x
12// This determines the range of responsibility for each node
13function lowbit(x: number): number {
14 return x & -x;
15}
16
17// Update operation: Add delta to position x
18// Time complexity: O(log n)
19function update(x: number, delta: number): void {
20 // Traverse all ancestors of node x
21 while (x <= size) {
22 tree[x] += delta;
23 x += lowbit(x); // Move to next node that needs updating
24 }
25}
26
27// Query operation: Get prefix sum from index 1 to x
28// Time complexity: O(log n)
29function query(x: number): number {
30 let sum = 0;
31 // Traverse all nodes that contribute to the sum
32 while (x > 0) {
33 sum += tree[x];
34 x -= lowbit(x); // Move to parent node
35 }
36 return sum;
37}
38
39// Count pairs (i, j) where i < j and nums1[i] - nums1[j] <= nums2[i] - nums2[j] + diff
40// Rearranging: nums1[i] - nums2[i] <= nums1[j] - nums2[j] + diff
41function numberOfPairs(nums1: number[], nums2: number[], diff: number): number {
42 // Create BIT with size 100000 to handle range of possible values
43 initializeBIT(100000);
44
45 let pairCount = 0;
46
47 // Process each element
48 for (let i = 0; i < nums1.length; i++) {
49 // Calculate difference for current position
50 const currentDiff = nums1[i] - nums2[i];
51
52 // Count how many previous elements satisfy the condition
53 // We need to count elements with value <= currentDiff + diff
54 // Add 40000 offset to handle negative values (shift to positive range)
55 pairCount += query(currentDiff + diff + 40000);
56
57 // Add current difference to the tree for future queries
58 update(currentDiff + 40000, 1);
59 }
60
61 return pairCount;
62}
63
Time and Space Complexity
Time Complexity: O(n × log n)
The solution iterates through the input arrays once with n
elements. For each element:
- The
query
operation on the Binary Indexed Tree takesO(log n)
time, as it traverses at mostlog n
nodes by repeatedly subtractinglowbit(x)
until reaching 0 - The
update
operation also takesO(log n)
time, as it traverses at mostlog n
nodes by repeatedly addinglowbit(x)
until exceeding the tree size
Since we perform both operations for each of the n
elements, the total time complexity is O(n × log n)
.
Space Complexity: O(10^5)
The space complexity is determined by:
- The Binary Indexed Tree array
c
which has a fixed size of10^5 + 1
elements, givingO(10^5)
space - Other variables like
ans
,v
,a
,b
useO(1)
space
Therefore, the overall space complexity is O(10^5)
, which is constant with respect to the input size n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Negative Values
The Pitfall: The most common mistake is forgetting to handle negative values properly when using a Binary Indexed Tree. Since BIT uses 1-based indexing and requires positive indices, directly using v = nums1[i] - nums2[i]
as an index will cause runtime errors when v
is negative.
Example of Wrong Implementation:
# WRONG - This will fail when v is negative
for a, b in zip(nums1, nums2):
v = a - b
ans += tree.query(v + diff) # Can be negative!
tree.update(v, 1) # Can be negative!
The Solution: Add a sufficient offset to ensure all indices are positive. The code uses 40000 as an offset because:
- The constraint typically has
|nums1[i]|, |nums2[i]| <= 10^4
- The minimum possible value of
v = nums1[i] - nums2[i]
is-10^4 - 10^4 = -2*10^4
- Adding 40000 ensures all indices are positive:
-20000 + 40000 = 20000 > 0
2. Off-by-One Error in BIT Size
The Pitfall: Declaring the BIT with insufficient size or not accounting for the offset properly.
Example of Wrong Implementation:
# WRONG - Size might be too small tree = BinaryIndexedTree(50000) # Not enough for all possible values with offset
The Solution: Calculate the maximum possible range:
- With values ranging from -20000 to 20000 and an offset of 40000
- The actual range becomes 20000 to 60000
- Add buffer for the diff value: could go up to 60000 + 10000 = 70000
- Use 100000 to be safe:
tree = BinaryIndexedTree(100000)
3. Forgetting to Apply Offset Consistently
The Pitfall: Applying the offset during query but forgetting it during update, or vice versa.
Example of Wrong Implementation:
# WRONG - Inconsistent offset usage
for a, b in zip(nums1, nums2):
v = a - b
ans += tree.query(v + diff + 40000) # Offset applied
tree.update(v, 1) # FORGOT offset here!
The Solution: Always apply the same offset to both query and update operations:
# CORRECT ans += tree.query(v + diff + 40000) tree.update(v + 40000, 1)
4. Misunderstanding the Inequality Direction
The Pitfall: Confusing which elements should be counted in the query. The transformed inequality v[i] <= v[j] + diff
means for each j
, we need to count all previous i
where v[i] <= v[j] + diff
.
Example of Wrong Implementation:
# WRONG - Querying the wrong range
for a, b in zip(nums1, nums2):
v = a - b
ans += tree.query(v + 40000) # Should be v + diff + 40000!
tree.update(v + 40000, 1)
The Solution: Remember that we're looking for all previous values that are at most v + diff
, not just v
:
ans += tree.query(v + diff + 40000) # Correct: v + diff
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
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
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!