2552. Count Increasing Quadruplets
Problem Description
The problem asks us to find the number of increasing quadruplets in an array nums
of size n
. A quadruplet (i, j, k, l) is considered increasing if:
- The indexes follow the relationship
0 <= i < j < k < l < n
- The values at those indices follow the relationship
nums[i] < nums[k] < nums[j] < nums[l]
Basically, we need to count how many combinations of four different indices (i, j, k, l)
in the array satisfy the conditions where the elements are strictly increasing with respect to those indices.
Intuition
To solve this problem, the intuitive approach would be to try every possible quadruplet and check if it satisfies the condition. However, this would result in an inefficient solution with a time complexity of O(n^4) which is not practical for large arrays.
Observing the problem, we can cleverly reduce the problem's complexity by dividing it into subproblems:
- For each pair
(j, k)
wherej < k
, count how manyl
(wherel > k
) exist such thatnums[j] < nums[l]
. Store this count since it will be common for alli
that come beforej
. We'll call this arrayf
. - Similarly, for each pair
(j, k)
wherej < k
, count how manyi
(wherei < j
) exist such thatnums[i] < nums[k]
. We need this to avoid recounting for differenti
with the samej
andk
. We'll call this arrayg
.
Now, for each pair (j, k)
, the number of valid quadruplets with indices (i, j, k, l)
is f[j][k] * g[j][k]
. By summing these products for all pairs (j, k)
, we get the total count of increasing quadruplets.
This approach significantly reduces the time complexity to O(n^3) as we perform three nested loops to count elements and use additional arrays f
and g
to store intermediate counts and avoid recomputation.
Learn more about Dynamic Programming and Prefix Sum patterns.
Solution Approach
The solution implements a dynamic programming approach to solve the problem efficiently. Two auxiliary matrices f
and g
are used to store the counts of valid l
indices for the second half of the quadruplet and valid i
indices for the first half of the quadruplet. The solution involves three main steps:
-
Counting the number of
l
elements for each(j, k)
pair wherej < k
, such thatnums[j] < nums[l]
. This count is stored in thef[j][k]
matrix. To avoid recomputation, we iterate backward from the penultimate index down to1
and update the count dynamically. -
Counting the number of
i
elements for each(j, k)
pair wherej < k
, such thatnums[i] < nums[k]
. This count is stored in theg[j][k]
matrix. Similarly, we iterate through the array and update the count as we move. -
Finally, for each
(j, k)
pair found in the previous steps, we multiply the values atf[j][k]
andg[j][k]
to get the number of valid quadruplets with indexes(i, j, k, l)
. We sum these products to derive the total count.
The code uses three nested loops: The outer two are for iterating over all possible (j, k)
pairs, and the inner ones are for calculating the counts that get stored in f
and g
. It's notable that relying on previously computed values and only updating them when necessary is a hallmark of dynamic programming which reduces time complexity.
Here's a more detailed step-by-step breakdown of what each part of the code is doing:
- We initialize the matrices
f
andg
as nxn matrices filled with 0s. - We iterate over variable
j
from 1 ton-3
for the first loop.- We calculate the count of
l
values greater thannums[j]
using a list comprehension and store it incnt
. - Then we iterate over
k
fromj+1
ton-2
and populate thef[j][k]
matrix usingcnt
. Ifnums[j] > nums[k]
, the count remains unchanged; else, we decreasecnt
by 1 as thatk
is not valid.
- We calculate the count of
- We iterate over variable
k
from 2 ton-2
for the second loop.- We calculate the count of
i
values smaller thannums[k]
using a list comprehension and store incnt
. - Then we iterate backward over
j
fromk-1
to 1, populating theg[j][k]
matrix usingcnt
. Ifnums[j] > nums[k]
, the count remains unchanged; else, we decreasecnt
by 1 for the same reason as above.
- We calculate the count of
- We calculate the sum of products of corresponding elements in
f
andg
to get the final count of increasing quadruplets.
By storing these intermediate results, the algorithm effectively avoids repetitive calculations and achieves a lower time complexity than the naive approach would permit.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example array nums
with elements [3, 1, 4, 2, 5].
Following the solution approach to count the number of increasing quadruplets:
-
Count number of
l
elements for each(j, k)
pair:-
Starting from
j = 1
(the second element):- We have
(j, k)
as (1, 2) i.e., elements1
and4
. There's one element greater than 4 in the remaining list[2, 5]
, which is5
. So,f[1][2] = 1
. - For
(j, k)
as (1, 3), there are no elements greater than2
in our remaining list[5]
, sof[1][3] = 0
.
- We have
-
Next with
j = 2
(the third element):- We only have one
(j, k)
pair (2, 3) sincek
should be greater thanj
. There is one element greater than2
which is5
, sof[2][3] = 1
.
- We only have one
-
-
Count number of
i
elements for each(j, k)
pair:-
Starting from
k = 2
(the third element):- Consider
i
values less than4
whenk = 2
. We have onei
at index 1 (element3
), sog[1][2] = 1
.
- Consider
-
Next with
k = 3
(the fourth element):- The values less than
2
whenk = 3
are3
and1
from indices 0 and 1, respectively. Thus,g[0][3] = 1
andg[1][3] = 1
.
- The values less than
-
-
Calculate the total count of increasing quadruplets:
- We only consider pairs where
j < k
, obtaining the products of correspondingf[j][k]
andg[j][k]
. - For
(j, k)
being (1, 2), we multiplyf[1][2]
andg[1][2]
which gives us1 * 1 = 1
. - For
(j, k)
being (1, 3), the multiplication isf[1][3] * g[1][3] = 0 * 1 = 0
. - For
(j, k)
being (2, 3), the multiplication isf[2][3] * g[0][3] = 1 * 1 = 1
.
- We only consider pairs where
By summing these up, we have 1 + 0 + 1 = 2
increasing quadruplets in the array.
As illustrated in this example, we utilized dynamic programming to systematically break down and solve the problem of finding all increasing quadruplets in an array, rather than naively comparing all possible combinations. Thanks to the intermediate storage of the count of l
and i
indices, we significantly reduce unnecessary recalculations and optimize the entire process.
Solution Implementation
1from typing import List
2
3class Solution:
4 def countQuadruplets(self, nums: List[int]) -> int:
5 # Determine the number of elements in the list.
6 length = len(nums)
7
8 # Initialize two matrices to store frequency counts.
9 freq_greater = [[0] * length for _ in range(length)]
10 freq_smaller = [[0] * length for _ in range(length)]
11
12 # Calculate the frequencies where nums[j] is greater than nums[k].
13 for j in range(1, length - 2):
14 # Count numbers greater than nums[j] starting from j+1
15 count_greater = sum(nums[elem] > nums[j] for elem in range(j + 1, length))
16 for k in range(j + 1, length - 1):
17 # If nums[j] is greater than nums[k], store the count.
18 if nums[j] > nums[k]:
19 freq_greater[j][k] = count_greater
20 # Otherwise, decrease the count as we move to the next k.
21 else:
22 count_greater -= 1
23
24 # Calculate the frequencies where nums[j] is smaller than nums[k].
25 for k in range(2, length - 1):
26 # Count numbers smaller than nums[k] up to index k
27 count_smaller = sum(nums[i] < nums[k] for i in range(k))
28 for j in range(k - 1, 0, -1):
29 # If nums[j] is greater than nums[k], store the count.
30 if nums[j] > nums[k]:
31 freq_smaller[j][k] = count_smaller
32 # Otherwise, decrease the count as we move to the next j.
33 else:
34 count_smaller -= 1
35
36 # Calculate the total number of quadruplets by multiplying the corresponding frequencies.
37 total_quadruplets = sum(
38 freq_greater[j][k] * freq_smaller[j][k] for j in range(1, length - 2) for k in range(j + 1, length - 1)
39 )
40
41 return total_quadruplets
42
1class Solution {
2 public long countQuadruplets(int[] nums) {
3 int len = nums.length;
4 // 'greaterThanCount' stores the number of elements greater than nums[j] after the index j.
5 int[][] greaterThanCount = new int[len][len];
6
7 // 'lessThanCount' stores the number of elements less than nums[k] before the index k.
8 int[][] lessThanCount = new int[len][len];
9
10 // Pre-compute the number of elements that are greater than the current element for future reference.
11 for (int j = 1; j < len - 2; ++j) {
12 int count = 0;
13 for (int l = j + 1; l < len; ++l) {
14 if (nums[l] > nums[j]) {
15 ++count;
16 }
17 }
18 for (int k = j + 1; k < len - 1; ++k) {
19 if (nums[j] > nums[k]) {
20 greaterThanCount[j][k] = count;
21 } else {
22 --count;
23 }
24 }
25 }
26
27 long quadrupletsCount = 0;
28 for (int k = 2; k < len - 1; ++k) {
29 int count = 0;
30 // Count elements less than nums[k] from the start of the array to the index k.
31 for (int i = 0; i < k; ++i) {
32 if (nums[i] < nums[k]) {
33 ++count;
34 }
35 }
36 for (int j = k - 1; j > 0; --j) {
37 if (nums[j] > nums[k]) {
38 lessThanCount[j][k] = count;
39 // Multiply the counts of valid numbers from both sides of 'k,'
40 // to get the number of valid quadruplets that include 'nums[k]' as the third member.
41 quadrupletsCount += (long) greaterThanCount[j][k] * lessThanCount[j][k];
42 } else {
43 --count;
44 }
45 }
46 }
47 return quadrupletsCount;
48 }
49}
50
1const int MAX_SIZE = 4001;
2int forwardCount[MAX_SIZE][MAX_SIZE];
3int backwardCount[MAX_SIZE][MAX_SIZE];
4
5class Solution {
6public:
7 long long countQuadruplets(vector<int>& nums) {
8 // Initialize variables.
9 int n = nums.size();
10 // Reset forward and backward count matrices.
11 memset(forwardCount, 0, sizeof forwardCount);
12 memset(backwardCount, 0, sizeof backwardCount);
13
14 // Loop to calculate forward counts.
15 for (int j = 1; j < n - 2; ++j) {
16 int count = 0; // Initialize counter for the number larger than nums[j]
17 // Count numbers larger than nums[j] after index j
18 for (int l = j + 1; l < n; ++l) {
19 if (nums[l] > nums[j]) {
20 ++count; // Increase count if the condition is true
21 }
22 }
23 // Calculate forward count for pairs (j, k)
24 for (int k = j + 1; k < n - 1; ++k) {
25 if (nums[j] > nums[k]) {
26 forwardCount[j][k] = count;
27 } else {
28 --count; // Decrease the count if nums[j] <= nums[k]
29 }
30 }
31 }
32
33 long long ans = 0; // Initialize answer
34 // Loop to calculate backward counts and the answer
35 for (int k = 2; k < n - 1; ++k) {
36 int count = 0; // Initialize counter for the number smaller than nums[k]
37 // Count numbers smaller than nums[k] before index k
38 for (int i = 0; i < k; ++i) {
39 if (nums[i] < nums[k]) {
40 ++count; // Increase count if the condition is true
41 }
42 }
43 // Calculate backward count for pairs (j, k) and update ans
44 for (int j = k - 1; j > 0; --j) {
45 if (nums[j] > nums[k]) {
46 backwardCount[j][k] = count;
47 // Update the final answer: the number of quadruplets where j < k
48 ans += 1ll * forwardCount[j][k] * backwardCount[j][k];
49 } else {
50 --count; // Decrease the count if nums[j] <= nums[k]
51 }
52 }
53 }
54 // Return the final count of quadruplets
55 return ans;
56 }
57};
58
1const MAX_SIZE = 4001;
2let forwardCount: number[][] = new Array(MAX_SIZE).fill(null).map(() => new Array(MAX_SIZE).fill(0));
3let backwardCount: number[][] = new Array(MAX_SIZE).fill(null).map(() => new Array(MAX_SIZE).fill(0));
4
5/**
6 * Counts the number of quadruplets that satisfy the given condition.
7 * @param nums An array of integers.
8 * @returns The number of valid quadruplets.
9 */
10function countQuadruplets(nums: number[]): number {
11 let n: number = nums.length;
12
13 // Reset forward and backward count matrices.
14 forwardCount = forwardCount.map(row => row.fill(0));
15 backwardCount = backwardCount.map(row => row.fill(0));
16
17 // Loop to calculate forward counts.
18 for (let j = 1; j < n - 2; ++j) {
19 let count: number = 0; // Counter for the number larger than nums[j].
20 // Count numbers larger than nums[j] after index j.
21 for (let l = j + 1; l < n; ++l) {
22 if (nums[l] > nums[j]) {
23 ++count; // Increase count if the condition is true.
24 }
25 }
26 // Calculate forward count for pairs (j, k).
27 for (let k = j + 1; k < n - 1; ++k) {
28 if (nums[j] > nums[k]) {
29 forwardCount[j][k] = count;
30 } else {
31 --count; // Decrease the count if nums[j] <= nums[k].
32 }
33 }
34 }
35
36 let ans: number = 0; // Initialize answer.
37 // Loop to calculate backward counts and the answer.
38 for (let k = 2; k < n - 1; ++k) {
39 let count: number = 0; // Initialize counter for the number smaller than nums[k].
40 // Count numbers smaller than nums[k] before index k.
41 for (let i = 0; i < k; ++i) {
42 if (nums[i] < nums[k]) {
43 ++count; // Increase count if the condition is true.
44 }
45 }
46 // Calculate backward count for pairs (j, k) and update ans.
47 for (let j = k - 1; j > 0; --j) {
48 if (nums[j] > nums[k]) {
49 backwardCount[j][k] = count;
50 // Update the final answer: the number of quadruplets where j < k.
51 ans += forwardCount[j][k] * backwardCount[j][k];
52 } else {
53 --count; // Decrease the count if nums[j] <= nums[k].
54 }
55 }
56 }
57 // Return the final count of quadruplets.
58 return ans;
59}
60
Time and Space Complexity
Time Complexity
For the given countQuadruplets
function, let's examine the time complexity step by step:
-
The generation of the matrices
f
andg
each requireO(n^2)
time since they involve creating two two-dimensional lists of sizen x n
. -
In the first loop (for
j
), the internal loop for counting (sum(nums[l] > nums[j] ...
) has a complexity ofO(n)
for eachj
iteration since it potentially scans all elements after indexj
. The nested loop fork
runs within the range ofj+1
ton-1
, making it also on averageO(n/2)
perj
iteration. Therefore, the total time complexity of this block isO(n^2 * n/2)
which simplifies toO(n^3)
. -
The second loop (for
k
) is similar in structure but runs the counting in reverse order. It has an inner loop for counting (sum(nums[i] < nums[k] ...)
) which also hasO(n)
complexity for eachk
, and the reversedj
loop again has an average complexity ofO(n/2)
perk
. This again leads toO(n^3)
for this block. -
The final sum operation iterates over the elements of the
f
andg
matrices, which hasO(n^2)
complexity since it involves a nested loop that traverses most of the matrix.
Adding all of these together, the dominant term is O(n^3)
since O(n^3)
is much larger than O(n^2)
as n
grows.
Thus, the total time complexity of the countQuadruplets
method is O(n^3)
.
Space Complexity
The space complexity is determined by the additional space needed aside from the input:
-
Two matrices
f
andg
each of sizen x n
are created, resulting inO(n^2)
space for each. Since they exist simultaneously, this amounts toO(2 * n^2)
space in total, which simplifies toO(n^2)
space. -
There are no other significant space-consuming objects or recursive calls that use additional space.
Hence, the space complexity of the method countQuadruplets
is O(n^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!