1995. Count Special Quadruplets
Problem Description
You are given an integer array nums
that is 0-indexed (meaning the first element is at index 0). Your task is to count how many distinct quadruplets of indices (a, b, c, d)
exist in the array that satisfy both of the following conditions:
- The sum of three elements equals the fourth element:
nums[a] + nums[b] + nums[c] == nums[d]
- The indices are in strictly increasing order:
a < b < c < d
A quadruplet consists of four different positions in the array. You need to find all valid combinations where the values at the first three positions add up to equal the value at the fourth position, while maintaining the strict ordering of indices.
For example, if nums = [1, 2, 3, 6]
, then the quadruplet (0, 1, 2, 3)
would be valid because:
nums[0] + nums[1] + nums[2] = 1 + 2 + 3 = 6 = nums[3]
- The indices satisfy
0 < 1 < 2 < 3
The solution uses a brute force approach with four nested loops to check every possible combination of four indices. For each valid combination where the indices are in increasing order, it checks if the sum condition is met. If both conditions are satisfied, the counter is incremented. The time complexity is O(n^4)
where n
is the length of the array.
Intuition
When we need to find all valid quadruplets with specific conditions, the most straightforward approach is to examine every possible combination of four indices. Since we're looking for groups of four elements where three sum to equal the fourth, and the indices must be in strict ascending order, we can think of this as a selection problem.
The key insight is that with the constraint a < b < c < d
, we're essentially choosing 4 positions from the array in order. This naturally leads to using nested loops where each inner loop starts from the position after the previous loop's current index. This ensures we never revisit the same combination and automatically maintains the ordering requirement.
For each combination of four indices, we simply check if nums[a] + nums[b] + nums[c] == nums[d]
. The equation has a fixed structure - we always sum the first three values and compare to the fourth. This makes the checking condition straightforward.
While we could optimize this problem using techniques like hash maps to store sums or two-pointer approaches, the brute force method with four nested loops is the most intuitive starting point. It directly translates the problem requirements into code: pick four indices in order, check if they satisfy the sum condition, and count the valid ones.
The range limits in each loop (n-3
for the first, n-2
for the second, etc.) ensure we always have enough elements remaining to form a complete quadruplet. This prevents index out-of-bounds errors and unnecessary iterations.
Solution Approach
The implementation uses a brute force approach with four nested loops to enumerate all possible quadruplets. Let's walk through the algorithm step by step:
-
Initialize variables: We set
ans = 0
to count valid quadruplets and store the array length inn
for convenience. -
First loop (index a): The outermost loop iterates from
0
ton-3
. We stop atn-3
because we need at least 3 more elements after positiona
to form a valid quadruplet. -
Second loop (index b): For each fixed
a
, we iterateb
froma+1
ton-2
. Starting froma+1
ensuresa < b
, and stopping atn-2
leaves room for indicesc
andd
. -
Third loop (index c): For each fixed pair
(a, b)
, we iteratec
fromb+1
ton-1
. This maintainsb < c
and leaves at least one position ford
. -
Fourth loop (index d): For each triple
(a, b, c)
, we iterated
fromc+1
ton
. This ensuresc < d
and completes our quadruplet. -
Check the sum condition: Inside the innermost loop, we check if
nums[a] + nums[b] + nums[c] == nums[d]
. If this condition is satisfied, we've found a valid quadruplet and increment our answer counter. -
Return the result: After examining all possible quadruplets, we return the total count.
The algorithm systematically explores all combinations where a < b < c < d
by design of the loop structure. Each inner loop starts one position after its parent loop's current index, which automatically enforces the ordering constraint without additional checks.
The time complexity is O(n^4)
due to the four nested loops, and the space complexity is O(1)
as we only use a constant amount of extra space for the counter and loop variables.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 5, 3, 2, 6]
.
We need to find all quadruplets (a, b, c, d)
where a < b < c < d
and nums[a] + nums[b] + nums[c] == nums[d]
.
Step-by-step iteration:
Starting with a = 0
(nums[0] = 1):
b = 1
(nums[1] = 5):c = 2
(nums[2] = 3):d = 3
: Check if 1 + 5 + 3 = 9 equals nums[3] = 2? No.d = 4
: Check if 1 + 5 + 3 = 9 equals nums[4] = 6? No.
c = 3
(nums[3] = 2):d = 4
: Check if 1 + 5 + 2 = 8 equals nums[4] = 6? No.
b = 2
(nums[2] = 3):c = 3
(nums[3] = 2):d = 4
: Check if 1 + 3 + 2 = 6 equals nums[4] = 6? Yes! Count = 1
Moving to a = 1
(nums[1] = 5):
b = 2
(nums[2] = 3):c = 3
(nums[3] = 2):d = 4
: Check if 5 + 3 + 2 = 10 equals nums[4] = 6? No.
The algorithm continues checking all remaining combinations, but no other valid quadruplets are found.
Final result: 1 valid quadruplet at indices (0, 2, 3, 4)
where 1 + 3 + 2 = 6
.
The nested loop structure ensures we check all possible combinations exactly once while maintaining the required index ordering. Each loop's starting point (a+1
, b+1
, c+1
) guarantees the strict inequality constraint is satisfied without additional conditional checks.
Solution Implementation
1class Solution:
2 def countQuadruplets(self, nums: List[int]) -> int:
3 """
4 Count the number of quadruplets (a, b, c, d) where:
5 - 0 <= a < b < c < d < n
6 - nums[a] + nums[b] + nums[c] == nums[d]
7
8 Args:
9 nums: List of integers
10
11 Returns:
12 Number of valid quadruplets
13 """
14 # Initialize counter and get array length
15 count = 0
16 n = len(nums)
17
18 # Iterate through all possible positions for first element
19 # Leave space for at least 3 more elements after it
20 for i in range(n - 3):
21 # Iterate through all possible positions for second element
22 # Start after first element, leave space for 2 more elements
23 for j in range(i + 1, n - 2):
24 # Iterate through all possible positions for third element
25 # Start after second element, leave space for 1 more element
26 for k in range(j + 1, n - 1):
27 # Iterate through all possible positions for fourth element
28 # Start after third element, go until end of array
29 for l in range(k + 1, n):
30 # Check if sum of first three elements equals fourth element
31 if nums[i] + nums[j] + nums[k] == nums[l]:
32 count += 1
33
34 return count
35
1class Solution {
2 /**
3 * Count quadruplets where nums[a] + nums[b] + nums[c] = nums[d]
4 * with indices satisfying a < b < c < d
5 *
6 * @param nums input array of integers
7 * @return count of valid quadruplets
8 */
9 public int countQuadruplets(int[] nums) {
10 int count = 0;
11 int n = nums.length;
12
13 // Iterate through all possible index positions for 'a'
14 // Stop at n-3 to ensure space for b, c, and d
15 for (int indexA = 0; indexA < n - 3; indexA++) {
16
17 // Iterate through all possible index positions for 'b'
18 // Start after 'a' and stop at n-2 to ensure space for c and d
19 for (int indexB = indexA + 1; indexB < n - 2; indexB++) {
20
21 // Iterate through all possible index positions for 'c'
22 // Start after 'b' and stop at n-1 to ensure space for d
23 for (int indexC = indexB + 1; indexC < n - 1; indexC++) {
24
25 // Iterate through all possible index positions for 'd'
26 // Start after 'c' and go until the end of array
27 for (int indexD = indexC + 1; indexD < n; indexD++) {
28
29 // Check if the sum of first three elements equals the fourth
30 if (nums[indexA] + nums[indexB] + nums[indexC] == nums[indexD]) {
31 count++;
32 }
33 }
34 }
35 }
36 }
37
38 return count;
39 }
40}
41
1class Solution {
2public:
3 int countQuadruplets(vector<int>& nums) {
4 int count = 0;
5 int n = nums.size();
6
7 // Iterate through all possible combinations of four indices (a, b, c, d)
8 // where a < b < c < d
9 for (int i = 0; i < n - 3; ++i) { // First index
10 for (int j = i + 1; j < n - 2; ++j) { // Second index
11 for (int k = j + 1; k < n - 1; ++k) { // Third index
12 for (int l = k + 1; l < n; ++l) { // Fourth index
13 // Check if the sum of first three elements equals the fourth element
14 if (nums[i] + nums[j] + nums[k] == nums[l]) {
15 ++count;
16 }
17 }
18 }
19 }
20 }
21
22 return count;
23 }
24};
25
1function countQuadruplets(nums: number[]): number {
2 let count: number = 0;
3 const n: number = nums.length;
4
5 // Iterate through all possible combinations of four indices (a, b, c, d)
6 // where a < b < c < d
7 for (let i = 0; i < n - 3; i++) { // First index (a)
8 for (let j = i + 1; j < n - 2; j++) { // Second index (b)
9 for (let k = j + 1; k < n - 1; k++) { // Third index (c)
10 for (let l = k + 1; l < n; l++) { // Fourth index (d)
11 // Check if the sum of first three elements equals the fourth element
12 // nums[a] + nums[b] + nums[c] = nums[d]
13 if (nums[i] + nums[j] + nums[k] === nums[l]) {
14 count++;
15 }
16 }
17 }
18 }
19 }
20
21 return count;
22}
23
Time and Space Complexity
Time Complexity: O(n^4)
The code uses four nested loops to iterate through all possible quadruplets (a, b, c, d)
where a < b < c < d
.
- The outer loop runs
n-3
times - The second loop runs approximately
n-2
times for each iteration of the first loop - The third loop runs approximately
n-1
times for each iteration of the second loop - The innermost loop runs approximately
n
times for each iteration of the third loop
This results in approximately (n-3) * (n-2) * (n-1) * n / 4!
iterations in the worst case, which simplifies to O(n^4)
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Variable
ans
to store the count - Variable
n
to store the length of the array - Loop variables
a
,b
,c
,d
No additional data structures are created that depend on the input size, so the space complexity is constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Loop Boundaries
One of the most common mistakes is setting incorrect loop boundaries, which can lead to either missing valid quadruplets or causing index out of bounds errors.
Incorrect Implementation:
# Wrong: This misses valid quadruplets or causes errors
for i in range(n): # Should be n-3
for j in range(i+1, n): # Should be n-2
for k in range(j+1, n): # Should be n-1
for l in range(k+1, n):
if nums[i] + nums[j] + nums[k] == nums[l]:
count += 1
Why it's wrong: The outer loops don't leave enough room for the remaining indices. For example, if i = n-1
, there's no room for j
, k
, and l
.
Correct Implementation:
for i in range(n - 3): # Leaves room for j, k, l
for j in range(i + 1, n - 2): # Leaves room for k, l
for k in range(j + 1, n - 1): # Leaves room for l
for l in range(k + 1, n):
if nums[i] + nums[j] + nums[k] == nums[l]:
count += 1
2. Using Same Index Multiple Times
Another pitfall is accidentally allowing the same index to be used multiple times in a quadruplet.
Incorrect Implementation:
# Wrong: Allows same index to be used multiple times
for i in range(n - 3):
for j in range(i, n - 2): # Should start at i+1, not i
for k in range(j, n - 1): # Should start at j+1, not j
for l in range(k, n): # Should start at k+1, not k
if nums[i] + nums[j] + nums[k] == nums[l]:
count += 1
Why it's wrong: Starting inner loops at the same index as the outer loop variable allows duplicate indices like (0, 0, 1, 2), which violates the requirement that all four indices must be distinct.
3. Optimization Opportunity: Early Termination
While not a bug, the brute force solution can be optimized with early termination when the sum becomes impossible.
Enhanced Implementation with Optimization:
def countQuadruplets(self, nums: List[int]) -> int:
count = 0
n = len(nums)
# Sort to enable early termination (optional optimization)
# Note: This changes indices, so only use if you don't need original positions
for i in range(n - 3):
for j in range(i + 1, n - 2):
for k in range(j + 1, n - 1):
target_sum = nums[i] + nums[j] + nums[k]
for l in range(k + 1, n):
if nums[l] == target_sum:
count += 1
return count
4. Memory-Efficient Alternative Using HashMap
For larger arrays, a more efficient approach uses a hashmap to reduce time complexity from O(n⁴) to O(n²).
Optimized Solution:
def countQuadruplets(self, nums: List[int]) -> int:
count = 0
n = len(nums)
# Use hashmap to store sums of pairs
for c in range(2, n - 1):
sum_count = {}
# Count all possible a+b where a < b < c
for b in range(1, c):
for a in range(b):
sum_ab = nums[a] + nums[b]
sum_count[sum_ab] = sum_count.get(sum_ab, 0) + 1
# For each d > c, check if nums[d] - nums[c] exists in our map
for d in range(c + 1, n):
target = nums[d] - nums[c]
if target in sum_count:
count += sum_count[target]
return count
This approach transforms the equation nums[a] + nums[b] + nums[c] = nums[d]
into nums[a] + nums[b] = nums[d] - nums[c]
and uses a hashmap to count matching pairs efficiently.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!