2261. K Divisible Elements Subarrays
Problem Description
The problem provides an integer array named nums
, along with two integers k
and p
. The objective is to find out how many distinct subarrays exist within nums
such that each subarray contains at most k
elements divisible by p
.
Subarrays are contiguous parts of the array, and they have to be non-empty. Distinct subarrays are those that differ in size or have at least one differing element at any position when compared.
It's important to understand:
- What a subarray is: a continuous sequence from the array.
- A distinct subarray: when compared to another, it has a different length or at least one different element at any index.
- The condition to be met: having at most
k
elements divisible byp
in each subarray.
The challenge is to consider all possible subarrays and count only the unique ones matching the divisibility constraint.
Intuition
The intuition behind the solution comprises two main aspects: iteration and uniqueness tracking.
-
Iteration: To find all possible subarrays, we iterate through the original array. Starting from each index in
nums
, we add elements one by one to the current subarray and check if they meet the condition (at mostk
elements divisible byp
). -
Uniqueness Tracking: As we keep extending these subarrays, we convert them into a string representation, by concatenating elements separated by commas, in order to record each unique one. This string acts as a unique key for the particular subarray.
-
Break Condition: An important aspect is knowing when to stop extending a subarray. This is determined by the count of elements divisible by
p
. As soon as we have more thank
such elements, we break the inner loop and proceed with the next starting index. -
Set for Uniqueness: We use a set data structure, since it automatically ensures that only unique subarrays are counted. We add the string representation of each valid subarray (up to
k
divisible elements) to this set.
The key lies in sequential scanning and the use of a set to ensure distinct subarrays are tracked and counted. The iteration ensures that every possible subarray configuration is considered, while the set data structure helps in avoiding duplicates and keeping the count of distinct subarrays only.
Learn more about Trie patterns.
Solution Approach
The solution approach uses a brute force method to find all possible subarrays and utilizes a set to store unique subarrays satisfying the condition. To understand the algorithm, let's break down the code:
- We have a nested loop; the outer loop starts with index
i
from0
to the end of the array. - For each position in the array, the inner loop adds elements to the subarray and checks if it still satisfies our condition.
- We keep track of the number of elements divisible by
p
using the variablecnt
. - To maintain the uniqueness, we create a string
t
to represent the current subarray. As we iterate with the inner loop, we append the current elementx
concatenated with a comma tot
. - After appending each element
x
, we check the divisibility condition. Ifcnt
exceedsk
, we stop extending that subarray and break the inner loop. - If
cnt
is still less than or equal tok
, we addt
to our sets
. - The set 's' maintains all unique subarrays encountered during the loops.
- Finally, we return the size of the set
s
, which gives us the number of unique subarrays.
Key Components:
- Brute Force Enumeration: The solution uses a brute force approach to generate all possible subarrays starting from each index in the original array.
- Set for Uniqueness: A set is employed to handle the uniqueness of subarrays, which means any duplicate representation is automatically neglected.
- String Representation: Subarrays are represented as strings, which allows for an easy comparison of uniqueness.
- Breaking Condition: The algorithm includes a breaking condition within the inner loop that halts further extension of a subarray once it no longer satisfies the divisibility condition.
The time complexity of this algorithm is O(n^3) in the worst case, where n
is the length of nums
. This is because we have a nested loop (O(n^2)) and string concatenation within the inner loop (O(n)), which can be quite inefficient for very large arrays. However, for smaller arrays or with constraints on k
, this approach is sufficient to find the correct answer.
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 take a small example to illustrate the solution approach.
Suppose we have the following inputs:
nums = [3, 1, 2, 3]
k = 2
p = 3
Now, we will walk through the algorithm step by step:
-
Initialize a set
s
to keep track of unique subarrays. -
Initialize an outer loop that starts with index
i
at0
and goes up to the length ofnums
. -
For each iteration of the outer loop, we start from the
i-th
element and initialize an inner loop that creates subarrays by adding one element at a time to the current subarray. -
Inside the inner loop, we have a
cnt
variable to count the number of elements divisible byp
and a stringt
for the subarray representation. -
Starting with index
i = 0
andnums[i] = 3
:- For the first element,
cnt
is incremented as3
is divisible byp = 3
. - The string
t
becomes"3,"
(representation of the subarray[3]
), and we add it to sets
.
- For the first element,
-
The inner loop extends the subarray to include the next element
1
, making it[3, 1]
:cnt
remains1
since1
is not divisible by3
.- The new subarray is represented by
"3,1,"
, and we add it to sets
.
-
We continue adding elements to the subarray and update
cnt
andt
accordingly:- Next, we include element
2
, resulting in subarray[3, 1, 2]
. As2
is not divisible by3
,cnt
stays the same. The stringt
is updated to"3,1,2,"
, and it is added tos
. - Then, we add the last element
3
, forming subarray[3, 1, 2, 3]
. Thecnt
now becomes2
since3
is divisible by3
. The stringt
is"3,1,2,3,"
, and it is also added tos
.
- Next, we include element
-
At this point, if there were more elements to add, and if adding another element divisible by
p
madecnt
exceedk
, the inner loop would break, and we would not add the new subarray representation tos
. -
The outer loop moves to the next starting index (
i = 1
) and repeats the process, generating all subarrays starting fromnums[1]
. -
The algorithm continues iterating this way, ensuring that all subarrays are considered, with only unique subarrays that have at most
k
divisible elements are added tos
. -
Once the loops are finished, the distinct subarrays in set
s
will be:
"3,"
"3,1,"
"3,1,2,"
"3,1,2,3,"
"1,"
"1,2,"
"1,2,3,"
"2,"
"2,3,"
"3,"
(which already exists and isn't counted again)
Thus, the size of set s
gives us the count of distinct subarrays, which is 9
in this example.
This walkthrough demonstrates how the brute force approach and set data structure come together to solve the given problem by considering every single possible subarray and maintaining their uniqueness.
Solution Implementation
1class Solution:
2 def countDistinct(self, nums: List[int], k: int, p: int) -> int:
3 # Initialize the length of 'nums' list
4 num_count = len(nums)
5 # Initialize a set to store unique sequence tuples
6 unique_sequences = set()
7
8 # Iterate through 'nums' list starting at each index
9 for start_index in range(num_count):
10 # Counter for numbers divisible by 'p'
11 divisible_count = 0
12 # Temporary string to build sequences
13 temp_sequence = ""
14
15 # Iterate through the rest of the list from current start_index
16 for number in nums[start_index:]:
17 # Increment the divisible count if the number is divisible by 'p'
18 divisible_count += number % p == 0
19 # If divisible_count exceeds k, break the loop as the condition is not met
20 if divisible_count > k:
21 break
22
23 # Append the current number to the sequence, separated by a comma
24 temp_sequence += str(number) + ","
25 # Add the current sequence to the set of unique sequences
26 unique_sequences.add(temp_sequence)
27
28 # Return the number of unique sequences found
29 return len(unique_sequences)
30
1class Solution {
2 public int countDistinct(int[] nums, int k, int p) {
3 // Initialize the number of elements in the array
4 int n = nums.length;
5 // Use a HashSet to store unique subarrays
6 Set<String> uniqueSubarrays = new HashSet<>();
7 // Loop through the array to start each possible subarray
8 for (int i = 0; i < n; ++i) {
9 int countDivisibleByP = 0; // Count of elements divisible by p
10 StringBuilder subarrayBuilder = new StringBuilder(); // Use StringBuilder to build the string representation of subarrays
11 // Loop to create subarrays starting at index i
12 for (int j = i; j < n; ++j) {
13 // If the current element is divisible by p, increment the count
14 if (nums[j] % p == 0) {
15 ++countDivisibleByP;
16 }
17 // If the count exceeds k, stop considering more elements in the subarray
18 if (countDivisibleByP > k) {
19 break;
20 }
21 // Add the current element to the subarray representation and include a separator
22 subarrayBuilder.append(nums[j]).append(",");
23 // Add the current subarray representation to the HashSet
24 uniqueSubarrays.add(subarrayBuilder.toString());
25 }
26 }
27 // The size of the HashSet gives the count of distinct subarrays
28 return uniqueSubarrays.size();
29 }
30}
31
1#include <vector>
2#include <unordered_set>
3#include <string>
4
5class Solution {
6public:
7 int countDistinct(vector<int>& nums, int maxDivisibleCount, int p) {
8 // Initialize a set to store the unique subarrays.
9 unordered_set<string> uniqueSubarrays;
10
11 // Size of the input array.
12 int numSize = nums.size();
13
14 // Iterate through the input array to start each subarray.
15 for (int startIdx = 0; startIdx < numSize; ++startIdx) {
16 // Initialize count for divisible numbers.
17 int divisibleCount = 0;
18
19 // Use a string to represent the subarray.
20 string subarrayStr;
21
22 // Continue to add elements to the subarray.
23 for (int endIdx = startIdx; endIdx < numSize; ++endIdx) {
24 // If the current element is divisible by p increment the count.
25 if (nums[endIdx] % p == 0 && ++divisibleCount > maxDivisibleCount) {
26 // If the divisible count exceeds 'k', we break out of the inner loop.
27 break;
28 }
29
30 // Append the current element and a comma to the subarray string.
31 subarrayStr += std::to_string(nums[endIdx]) + ",";
32
33 // Insert the current subarray into the set (this ensures uniqueness).
34 uniqueSubarrays.insert(subarrayStr);
35 }
36 }
37
38 // Return the number of unique subarrays.
39 return uniqueSubarrays.size();
40 }
41};
42
1/**
2 * Counts the number of distinct subarrays where the number of elements
3 * divisible by `p` does not exceed `k`.
4 * @param nums - An array of numbers to be processed.
5 * @param k - The maximum allowable number of elements divisible by `p` in a subarray.
6 * @param p - The divisor used to determine divisibility of elements in the subarray.
7 * @returns The count of distinct subarrays meeting the criteria.
8 */
9function countDistinct(nums: number[], k: number, p: number): number {
10 // Get the length of the input array.
11 const numsLength = nums.length;
12 // Initialize a Set to store unique subarray representations.
13 const uniqueSubarrays = new Set<string>();
14
15 // Iterate over the array to consider different starting points for subarrays.
16 for (let startIndex = 0; startIndex < numsLength; ++startIndex) {
17 // Initialize count for tracking the number of elements divisible by `p`.
18 let divisibleCount = 0;
19 // Initialize a temporary string to represent the subarray.
20 let subarrayRepresentation = '';
21
22 // Iterate over the array to consider different ending points for subarrays.
23 for (let endIndex = startIndex; endIndex < numsLength; ++endIndex) {
24 // If the current element is divisible by `p` and we've exceeded `k`, break out of the loop.
25 if (nums[endIndex] % p === 0 && ++divisibleCount > k) {
26 break;
27 }
28 // Add the current element to the subarray representation, followed by a comma.
29 subarrayRepresentation += nums[endIndex].toString() + ',';
30 // Add the current subarray representation to the Set of unique subarrays.
31 uniqueSubarrays.add(subarrayRepresentation);
32 }
33 }
34
35 // Return the size of the Set, which represents the number of unique subarrays.
36 return uniqueSubarrays.size;
37}
38
Time and Space Complexity
Time Complexity
The given Python code has a nested loop structure where the outer loop runs n
times (where n
is the length of the nums
list) and the inner loop can run up to n
times in the worst case. For each iteration of the inner loop, the code checks whether the current number is divisible by p
and breaks the loop if the count of numbers divisible by p
exceeds k
.
The +=
operator on a string inside the inner loop has a time complexity of O(m)
each time it is executed, where m
is the current length of the string t
. Since t
grows linearly with the number of iterations, the string concatenation inside the loop can have a quadratic effect in terms of the number of elements encountered.
Considering all these factors, the overall worst-case time complexity is O(n^3)
- this happens when all numbers are less than p
or when k
is large, allowing the inner loop to run for all elements without breaking early.
Space Complexity
The space complexity mainly comes from the set s
that stores unique strings constructed from the list elements. In the worst case, it can store all subarrays, and the size of the subarrays are incremental starting from 1 to n
. The space required for these strings could be considered as summing an arithmetic progression from 1 to n
, giving a space complexity of O(n*(n+1)/2)
, which simplifies to O(n^2)
.
Additionally, there is a minor space cost for the variables cnt
and t
, but they don't grow with n
in a way that would affect the overall space complexity.
Therefore, the overall space complexity of the code is O(n^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the following array represent a max heap?
Recommended Readings
Trie Introduction Imagine you have a magic dictionary where as soon as you start spelling a word it immediately tells you how many words that start with those letters How cool would that be This is exactly the kind of magic a data structure called trie performs What's a Trie
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
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!