2552. Count Increasing Quadruplets
Problem Description
You are given a 0-indexed integer array nums
of size n
that contains all numbers from 1
to n
. Your task is to count the number of increasing quadruplets in this array.
A quadruplet (i, j, k, l)
is considered "increasing" if it satisfies both of these conditions:
- The indices follow the order:
0 <= i < j < k < l < n
- The values at these indices follow the specific pattern:
nums[i] < nums[k] < nums[j] < nums[l]
Note that this is not a standard increasing sequence. The pattern requires that:
- The value at index
i
is less than the value at indexk
- The value at index
k
is less than the value at indexj
- The value at index
j
is less than the value at indexl
This creates a "132-4" pattern where indices j
and k
are swapped in the value comparison compared to their positional order.
The solution uses dynamic programming with preprocessing. It fixes the middle two indices j
and k
, then:
- Uses array
f[j][k]
to store how many indicesl > k
havenums[l] > nums[j]
- Uses array
g[j][k]
to store how many indicesi < j
havenums[i] < nums[k]
The total count of valid quadruplets is the sum of f[j][k] * g[j][k]
for all valid pairs of j
and k
.
Intuition
The key insight is recognizing that directly checking all possible quadruplets would require O(n^4) time, which is inefficient. Instead, we can observe that the pattern nums[i] < nums[k] < nums[j] < nums[l]
has an interesting structure where the middle two elements j
and k
are "inverted" in their value relationship compared to their index positions.
This suggests we should fix the middle two indices j
and k
as our pivot points. Once we fix these two positions where j < k
but nums[j] > nums[k]
, we need to:
- Find all valid
i
values to the left ofj
wherenums[i] < nums[k]
- Find all valid
l
values to the right ofk
wherenums[l] > nums[j]
The number of valid quadruplets for a fixed (j, k)
pair equals the product of valid i
choices and valid l
choices, since each i
can be paired with each l
independently.
The challenge is computing these counts efficiently. We notice that:
- When iterating through different
k
values for a fixedj
, we can maintain a running count of how many elements to the right ofk
are greater thannums[j]
- Similarly, when iterating through different
j
values for a fixedk
, we can maintain a running count of how many elements to the left ofj
are less thannums[k]
This leads to the preprocessing approach where we build two 2D arrays:
f[j][k]
: counts elements after positionk
that are greater thannums[j]
g[j][k]
: counts elements before positionj
that are less thannums[k]
By precomputing these values, we reduce the problem to O(n^2) time complexity, making it much more efficient than the naive approach.
Learn more about Dynamic Programming and Prefix Sum patterns.
Solution Approach
The solution uses preprocessing with two 2D arrays to efficiently count the quadruplets. Let's walk through the implementation step by step:
Step 1: Initialize the arrays
- Create two
n × n
arraysf
andg
initialized with zeros f[j][k]
will store the count of indicesl > k
wherenums[l] > nums[j]
g[j][k]
will store the count of indicesi < j
wherenums[i] < nums[k]
Step 2: Build array f
For each valid j
position (from 1
to n-3
):
- First, count all elements after
j
that are greater thannums[j]
:cnt = sum(nums[l] > nums[j] for l in range(j + 1, n))
- Then iterate through each
k
position afterj
(fromj+1
ton-2
):- If
nums[j] > nums[k]
(the required condition), setf[j][k] = cnt
- Otherwise, decrement
cnt
by 1 (sincenums[k]
itself was counted but doesn't satisfy the condition anymore as we movek
forward)
- If
The key optimization here is maintaining a running count. As k
increases, if nums[k] >= nums[j]
, then nums[k]
was previously counted in cnt
but shouldn't be counted for positions after k
, so we decrement it.
Step 3: Build array g
For each valid k
position (from 2
to n-2
):
- First, count all elements before
k
that are less thannums[k]
:cnt = sum(nums[i] < nums[k] for i in range(k))
- Then iterate through each
j
position beforek
in reverse (fromk-1
down to1
):- If
nums[j] > nums[k]
(the required condition), setg[j][k] = cnt
- Otherwise, decrement
cnt
by 1 (sincenums[j]
itself was counted but doesn't satisfy the condition as we movej
backward)
- If
Step 4: Calculate the final answer The total count of quadruplets is:
sum(f[j][k] * g[j][k] for j in range(1, n - 2) for k in range(j + 1, n - 1))
For each valid (j, k)
pair where j < k
and nums[j] > nums[k]
, multiply:
- The number of valid
l
values (stored inf[j][k]
) - The number of valid
i
values (stored ing[j][k]
)
This gives us all possible quadruplets for that specific (j, k)
pair.
Time Complexity: O(n²) for preprocessing both arrays and computing the final sum Space Complexity: O(n²) for storing the two 2D arrays
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 a concrete example: nums = [4, 1, 3, 2, 5]
We need to find quadruplets (i, j, k, l)
where i < j < k < l
and nums[i] < nums[k] < nums[j] < nums[l]
.
Step 1: Initialize arrays
- Create 5×5 arrays
f
andg
(all zeros initially)
Step 2: Build array f[j][k]
(counts of l > k
where nums[l] > nums[j]
)
For j = 1
(nums[1] = 1
):
- Count elements after j=1 that are > 1: positions 2,3,4 have values 3,2,5, all > 1, so
cnt = 3
- For
k = 2
(nums[2] = 3
): Sincenums[1]=1
NOT >nums[2]=3
, skip and decrementcnt
to 2 - For
k = 3
(nums[3] = 2
): Sincenums[1]=1
NOT >nums[3]=2
, skip and decrementcnt
to 1
For j = 2
(nums[2] = 3
):
- Count elements after j=2 that are > 3: only position 4 has value 5 > 3, so
cnt = 1
- For
k = 3
(nums[3] = 2
): Sincenums[2]=3
>nums[3]=2
✓, setf[2][3] = 1
Step 3: Build array g[j][k]
(counts of i < j
where nums[i] < nums[k]
)
For k = 2
(nums[2] = 3
):
- Count elements before k=2 that are < 3: positions 0,1 have values 4,1, only 1 < 3, so
cnt = 1
- For
j = 1
(nums[1] = 1
): Sincenums[1]=1
NOT >nums[2]=3
, skip and decrementcnt
to 0
For k = 3
(nums[3] = 2
):
- Count elements before k=3 that are < 2: only position 1 has value 1 < 2, so
cnt = 1
- For
j = 2
(nums[2] = 3
): Sincenums[2]=3
>nums[3]=2
✓, setg[2][3] = 1
- For
j = 1
(nums[1] = 1
): Sincenums[1]=1
NOT >nums[3]=2
, skip
Step 4: Calculate total quadruplets
Sum up f[j][k] * g[j][k]
for all valid (j,k) pairs:
- Only
(j=2, k=3)
has bothf[2][3] = 1
andg[2][3] = 1
- Result:
1 * 1 = 1
Verification: The quadruplet is (i=1, j=2, k=3, l=4)
with values (1, 3, 2, 5)
.
- Check:
nums[1]=1
<nums[3]=2
<nums[2]=3
<nums[4]=5
✓
The algorithm correctly finds 1 increasing quadruplet.
Solution Implementation
1class Solution:
2 def countQuadruplets(self, nums: List[int]) -> int:
3 n = len(nums)
4
5 # f[j][k] stores count of elements > nums[j] that come after k
6 # where j < k and nums[j] > nums[k]
7 greater_after = [[0] * n for _ in range(n)]
8
9 # g[j][k] stores count of elements < nums[k] that come before j
10 # where j < k and nums[j] > nums[k]
11 less_before = [[0] * n for _ in range(n)]
12
13 # Build greater_after matrix
14 # For each j, count elements greater than nums[j] that appear after position k
15 for j in range(1, n - 2):
16 # Initially count all elements after j that are greater than nums[j]
17 count = sum(nums[l] > nums[j] for l in range(j + 1, n))
18
19 for k in range(j + 1, n - 1):
20 if nums[j] > nums[k]:
21 # If nums[j] > nums[k], store the count
22 greater_after[j][k] = count
23 else:
24 # If nums[j] <= nums[k], nums[k] was counted but shouldn't be
25 # Decrement count as we move forward
26 count -= 1
27
28 # Build less_before matrix
29 # For each k, count elements less than nums[k] that appear before position j
30 for k in range(2, n - 1):
31 # Initially count all elements before k that are less than nums[k]
32 count = sum(nums[i] < nums[k] for i in range(k))
33
34 for j in range(k - 1, 0, -1):
35 if nums[j] > nums[k]:
36 # If nums[j] > nums[k], store the count
37 less_before[j][k] = count
38 else:
39 # If nums[j] <= nums[k], nums[j] was counted but shouldn't be
40 # Decrement count as we move backward
41 count -= 1
42
43 # Calculate total quadruplets
44 # For each valid (j, k) pair where nums[j] > nums[k]:
45 # - greater_after[j][k] gives count of valid l positions
46 # - less_before[j][k] gives count of valid i positions
47 # Multiply them to get all valid (i, j, k, l) quadruplets
48 total = sum(
49 greater_after[j][k] * less_before[j][k]
50 for j in range(1, n - 2)
51 for k in range(j + 1, n - 1)
52 )
53
54 return total
55
1class Solution {
2 public long countQuadruplets(int[] nums) {
3 int n = nums.length;
4
5 // countGreaterOnRight[j][k]: For indices j < k, stores count of elements > nums[j] that appear after k
6 int[][] countGreaterOnRight = new int[n][n];
7
8 // countLessOnLeft[j][k]: For indices j < k, stores count of elements < nums[k] that appear before j
9 int[][] countLessOnLeft = new int[n][n];
10
11 // Precompute countGreaterOnRight array
12 // For each j, we count how many elements after each k are greater than nums[j]
13 for (int j = 1; j < n - 2; j++) {
14 // Initially count all elements after j that are greater than nums[j]
15 int greaterCount = 0;
16 for (int idx = j + 1; idx < n; idx++) {
17 if (nums[idx] > nums[j]) {
18 greaterCount++;
19 }
20 }
21
22 // For each k after j, store the count if nums[j] > nums[k] (valid j-k pair)
23 for (int k = j + 1; k < n - 1; k++) {
24 if (nums[j] > nums[k]) {
25 // nums[j] > nums[k], so this is a valid (j, k) pair
26 countGreaterOnRight[j][k] = greaterCount;
27 } else {
28 // nums[k] >= nums[j], so we need to exclude nums[k] from our count
29 greaterCount--;
30 }
31 }
32 }
33
34 long result = 0;
35
36 // Compute countLessOnLeft and accumulate the result
37 for (int k = 2; k < n - 1; k++) {
38 // Count all elements before k that are less than nums[k]
39 int lessCount = 0;
40 for (int idx = 0; idx < k; idx++) {
41 if (nums[idx] < nums[k]) {
42 lessCount++;
43 }
44 }
45
46 // For each j before k (going backwards), compute valid quadruplets
47 for (int j = k - 1; j > 0; j--) {
48 if (nums[j] > nums[k]) {
49 // nums[j] > nums[k], so this is a valid (j, k) pair
50 countLessOnLeft[j][k] = lessCount;
51
52 // Count valid quadruplets:
53 // countLessOnLeft[j][k] gives us valid i's (where i < j and nums[i] < nums[k])
54 // countGreaterOnRight[j][k] gives us valid l's (where k < l and nums[j] < nums[l])
55 result += (long) countGreaterOnRight[j][k] * countLessOnLeft[j][k];
56 } else {
57 // nums[j] <= nums[k], so we need to exclude nums[j] from our count
58 lessCount--;
59 }
60 }
61 }
62
63 return result;
64 }
65}
66
1const int MAX_SIZE = 4001;
2int countGreaterAfterJ[MAX_SIZE][MAX_SIZE]; // f[j][k]: count of elements > nums[j] after position k
3int countLessBeforeK[MAX_SIZE][MAX_SIZE]; // g[j][k]: count of elements < nums[k] before position j
4
5class Solution {
6public:
7 long long countQuadruplets(vector<int>& nums) {
8 int n = nums.size();
9
10 // Initialize arrays to zero
11 memset(countGreaterAfterJ, 0, sizeof countGreaterAfterJ);
12 memset(countLessBeforeK, 0, sizeof countLessBeforeK);
13
14 // Precompute for each valid j position:
15 // For each k > j where nums[j] > nums[k], count how many l > k satisfy nums[l] > nums[j]
16 for (int j = 1; j < n - 2; ++j) {
17 // Count all elements after j that are greater than nums[j]
18 int greaterCount = 0;
19 for (int l = j + 1; l < n; ++l) {
20 if (nums[l] > nums[j]) {
21 ++greaterCount;
22 }
23 }
24
25 // For each k position after j
26 for (int k = j + 1; k < n - 1; ++k) {
27 if (nums[j] > nums[k]) {
28 // Store count of valid l positions (where nums[l] > nums[j] and l > k)
29 countGreaterAfterJ[j][k] = greaterCount;
30 } else {
31 // If nums[k] >= nums[j], this k contributes to reducing available l positions
32 --greaterCount;
33 }
34 }
35 }
36
37 long long totalQuadruplets = 0;
38
39 // For each k position, find valid (i, j) pairs and calculate contribution
40 for (int k = 2; k < n - 1; ++k) {
41 // Count all elements before k that are less than nums[k]
42 int lessCount = 0;
43 for (int i = 0; i < k; ++i) {
44 if (nums[i] < nums[k]) {
45 ++lessCount;
46 }
47 }
48
49 // For each j position before k (scanning backwards)
50 for (int j = k - 1; j > 0; --j) {
51 if (nums[j] > nums[k]) {
52 // Store count of valid i positions (where nums[i] < nums[k] and i < j)
53 countLessBeforeK[j][k] = lessCount;
54
55 // Add contribution: number of valid i's × number of valid l's
56 // This counts quadruplets (i, j, k, l) where:
57 // i < j < k < l and nums[i] < nums[k] < nums[j] < nums[l]
58 totalQuadruplets += 1LL * countGreaterAfterJ[j][k] * countLessBeforeK[j][k];
59 } else {
60 // If nums[j] <= nums[k], this j contributes to reducing available i positions
61 --lessCount;
62 }
63 }
64 }
65
66 return totalQuadruplets;
67 }
68};
69
1const MAX_SIZE = 4001;
2// countGreaterAfterJ[j][k]: count of elements > nums[j] after position k
3const countGreaterAfterJ: number[][] = Array(MAX_SIZE).fill(0).map(() => Array(MAX_SIZE).fill(0));
4// countLessBeforeK[j][k]: count of elements < nums[k] before position j
5const countLessBeforeK: number[][] = Array(MAX_SIZE).fill(0).map(() => Array(MAX_SIZE).fill(0));
6
7function countQuadruplets(nums: number[]): number {
8 const n = nums.length;
9
10 // Reset arrays to zero
11 for (let i = 0; i < MAX_SIZE; i++) {
12 for (let j = 0; j < MAX_SIZE; j++) {
13 countGreaterAfterJ[i][j] = 0;
14 countLessBeforeK[i][j] = 0;
15 }
16 }
17
18 // Precompute for each valid j position:
19 // For each k > j where nums[j] > nums[k], count how many l > k satisfy nums[l] > nums[j]
20 for (let j = 1; j < n - 2; j++) {
21 // Count all elements after j that are greater than nums[j]
22 let greaterCount = 0;
23 for (let l = j + 1; l < n; l++) {
24 if (nums[l] > nums[j]) {
25 greaterCount++;
26 }
27 }
28
29 // For each k position after j
30 for (let k = j + 1; k < n - 1; k++) {
31 if (nums[j] > nums[k]) {
32 // Store count of valid l positions (where nums[l] > nums[j] and l > k)
33 countGreaterAfterJ[j][k] = greaterCount;
34 } else {
35 // If nums[k] >= nums[j], this k contributes to reducing available l positions
36 greaterCount--;
37 }
38 }
39 }
40
41 let totalQuadruplets = 0;
42
43 // For each k position, find valid (i, j) pairs and calculate contribution
44 for (let k = 2; k < n - 1; k++) {
45 // Count all elements before k that are less than nums[k]
46 let lessCount = 0;
47 for (let i = 0; i < k; i++) {
48 if (nums[i] < nums[k]) {
49 lessCount++;
50 }
51 }
52
53 // For each j position before k (scanning backwards)
54 for (let j = k - 1; j > 0; j--) {
55 if (nums[j] > nums[k]) {
56 // Store count of valid i positions (where nums[i] < nums[k] and i < j)
57 countLessBeforeK[j][k] = lessCount;
58
59 // Add contribution: number of valid i's × number of valid l's
60 // This counts quadruplets (i, j, k, l) where:
61 // i < j < k < l and nums[i] < nums[k] < nums[j] < nums[l]
62 totalQuadruplets += countGreaterAfterJ[j][k] * countLessBeforeK[j][k];
63 } else {
64 // If nums[j] <= nums[k], this j contributes to reducing available i positions
65 lessCount--;
66 }
67 }
68 }
69
70 return totalQuadruplets;
71}
72
Time and Space Complexity
The time complexity is O(n²)
and the space complexity is O(n²)
, where n
is the length of the array.
Time Complexity Analysis:
- The algorithm uses two main preprocessing loops:
- First loop: For each
j
from1
ton-2
, we iterate throughk
fromj+1
ton-1
. The initial countingsum(nums[l] > nums[j] for l in range(j + 1, n))
takesO(n)
time, but this is done once perj
. The inner loop runsO(n)
times withO(1)
operations each. Total for this section:O(n²)
- Second loop: For each
k
from2
ton-1
, we iterate throughj
fromk-1
down to1
. Similar to the first loop, the initial counting takesO(n)
time once perk
, and the inner loop runsO(n)
times withO(1)
operations. Total for this section:O(n²)
- First loop: For each
- The final summation iterates through all valid
(j, k)
pairs wherej
ranges from1
ton-2
andk
ranges fromj+1
ton-1
, which isO(n²)
pairs withO(1)
work per pair. - Overall time complexity:
O(n²) + O(n²) + O(n²) = O(n²)
Space Complexity Analysis:
- Two 2D arrays
f
andg
are created, each of sizen × n
- Total space used:
2 × n² = O(n²)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Count Updates When Building the Arrays
A critical pitfall is misunderstanding when to decrement the count while building the f
and g
arrays. Many developers make the mistake of decrementing the count in the wrong condition.
Incorrect Implementation:
# WRONG: Decrementing when nums[j] > nums[k]
for k in range(j + 1, n - 1):
if nums[j] > nums[k]:
greater_after[j][k] = count
count -= 1 # WRONG: Should not decrement here
Why it's wrong: When nums[j] > nums[k]
, we want to keep all elements greater than nums[j]
in our count. We only decrement when nums[k] >= nums[j]
because nums[k]
itself was initially counted but won't be available for future k
positions.
Correct Implementation:
for k in range(j + 1, n - 1):
if nums[j] > nums[k]:
greater_after[j][k] = count
else:
# Only decrement when nums[k] >= nums[j]
count -= 1
2. Off-by-One Errors in Loop Boundaries
The loop boundaries are crucial for this problem. Common mistakes include:
Incorrect Loop Ranges:
# WRONG: Missing valid positions
for j in range(n - 2): # Should start from 1, not 0
count = sum(nums[l] > nums[j] for l in range(j + 1, n))
for k in range(j + 1, n): # Should end at n-1, not n
Correct Loop Ranges:
# j must be at least index 1 (need room for i before it)
# j must be at most n-3 (need room for k and l after it)
for j in range(1, n - 2):
count = sum(nums[l] > nums[j] for l in range(j + 1, n))
# k must be at most n-2 (need room for l after it)
for k in range(j + 1, n - 1):
3. Confusing the Pattern Requirements
The pattern nums[i] < nums[k] < nums[j] < nums[l]
is NOT a standard increasing sequence. It's easy to mistakenly check for nums[i] < nums[j] < nums[k] < nums[l]
.
Wrong Understanding:
# WRONG: Checking standard increasing order if nums[i] < nums[j] and nums[j] < nums[k] and nums[k] < nums[l]: count += 1
Correct Understanding: The pattern specifically requires:
nums[i] < nums[k]
(comparing positions i and k)nums[k] < nums[j]
(k value is less than j value, despite k > j in position)nums[j] < nums[l]
(comparing positions j and l)
4. Inefficient Initial Count Calculation
While the provided solution is correct, a common inefficiency is recalculating the initial count from scratch for every j or k value:
Less Efficient:
for j in range(1, n - 2):
# Recalculating from scratch every time
count = sum(nums[l] > nums[j] for l in range(j + 1, n))
More Efficient Alternative:
# Pre-compute suffix counts for all positions
suffix_greater = [0] * n
for j in range(n - 2, -1, -1):
suffix_greater[j] = sum(1 for l in range(j + 1, n) if nums[l] > nums[j])
5. Memory Optimization Overlooked
The solution uses O(n²) space for two 2D arrays. For very large inputs, this might cause memory issues. Consider that many entries in the arrays remain zero (when nums[j] <= nums[k]
).
Space-Optimized Alternative Using Dictionary:
greater_after = {} # Use sparse representation
less_before = {}
# Only store non-zero values
if nums[j] > nums[k]:
greater_after[(j, k)] = count
# Later when calculating result
total = sum(
greater_after.get((j, k), 0) * less_before.get((j, k), 0)
for j in range(1, n - 2)
for k in range(j + 1, n - 1)
)
This optimization is particularly useful when the array has many positions where nums[j] <= nums[k]
, resulting in sparse matrices.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!