3350. Adjacent Increasing Subarrays Detection II
Problem Description
You are given an array nums
containing n
integers. Your goal is to find the maximum value of k
such that you can find two adjacent subarrays, each of length k
, where both subarrays are strictly increasing.
More specifically:
- You need to find two subarrays starting at indices
a
andb
- These subarrays must be adjacent, meaning
b = a + k
(the second subarray starts immediately after the first one ends) - Both subarrays
nums[a..a + k - 1]
andnums[b..b + k - 1]
must be strictly increasing (each element is greater than the previous one) - You want to find the maximum possible value of
k
that satisfies these conditions
For example, if nums = [1, 2, 3, 2, 3, 4]
:
- You can find two adjacent strictly increasing subarrays of length 2:
[1, 2]
and[3, 2]
- but wait,[3, 2]
is not strictly increasing - You can find
[2, 3]
starting at index 1 and[2, 3, 4]
starting at index 3, but they're not adjacent - The maximum
k
would be 1, as you can find adjacent subarrays[3]
and[2]
(both of length 1 are trivially strictly increasing)
The solution approach tracks the lengths of consecutive strictly increasing sequences in the array. For each position, it maintains:
cur
: the current length of the strictly increasing sequencepre
: the length of the previous strictly increasing sequenceans
: the maximumk
found so far
When a strictly increasing sequence ends (either at the array's end or when nums[i] >= nums[i+1]
), the code updates the answer by considering:
cur // 2
: the possibility of splitting the current increasing sequence into two equal adjacent partsmin(pre, cur)
: the possibility of using the end of the previous sequence and the beginning of the current sequence as the two adjacent subarrays
Intuition
The key insight is that two adjacent strictly increasing subarrays of length k
can only come from two scenarios:
-
Within a single increasing sequence: If we have a long strictly increasing sequence, we can split it into two adjacent parts. For a sequence of length
n
, the maximumk
we can achieve isn // 2
(we split it as evenly as possible). -
Across two consecutive increasing sequences: When one increasing sequence ends and another begins, we can take the tail of the first sequence and the head of the second sequence as our two adjacent subarrays. The maximum
k
here is limited by the shorter of the two sequences, hencemin(pre, cur)
.
To understand why this works, consider that as we traverse the array, we're essentially identifying all strictly increasing segments. For example, in [1, 2, 3, 1, 2, 3, 4]
, we have two segments: [1, 2, 3]
and [1, 2, 3, 4]
.
At each boundary between segments (when nums[i] >= nums[i+1]
), we have an opportunity to check:
- Can we split the current segment into two equal adjacent parts? This gives us
cur // 2
- Can we use parts from both the previous and current segments? This gives us
min(pre, cur)
By tracking these values throughout our traversal and keeping the maximum, we ensure we don't miss any valid configuration. The algorithm is efficient because it processes each element exactly once, accumulating information about increasing sequences as it goes.
The beauty of this approach is that it reduces a seemingly complex problem about finding specific subarray patterns into a simpler problem of tracking the lengths of increasing sequences and making local decisions at their boundaries.
Learn more about Binary Search patterns.
Solution Approach
The solution uses a single-pass algorithm with three variables to track the state as we iterate through the array:
Variables:
ans
: Stores the maximum value ofk
found so farpre
: Length of the previous strictly increasing sequencecur
: Length of the current strictly increasing sequence being built
Algorithm Steps:
-
Initialize all variables to 0:
ans = pre = cur = 0
-
Iterate through the array using enumeration to get both index
i
and valuex
:- Increment
cur
by 1 for each element (extending the current increasing sequence)
- Increment
-
Check for sequence boundary at each position:
- A boundary occurs when either:
- We reach the last element (
i == len(nums) - 1
), or - The current element is greater than or equal to the next element (
x >= nums[i + 1]
)
- We reach the last element (
- A boundary occurs when either:
-
When a boundary is found, update the answer:
- Calculate
max(ans, cur // 2, min(pre, cur))
cur // 2
: Maximumk
if we split the current sequence into two adjacent partsmin(pre, cur)
: Maximumk
using the tail of previous and head of current sequence- Keep the maximum among these and the existing
ans
- Calculate
-
Update state for next iteration:
- Set
pre = cur
(current sequence becomes the previous) - Reset
cur = 0
(start counting the next sequence)
- Set
Example Walkthrough:
For nums = [2, 5, 7, 8, 9, 2, 3, 4, 3, 1]
:
- Sequence 1:
[2, 5, 7, 8, 9]
(length 5)- At boundary,
ans = max(0, 5//2, min(0, 5)) = 2
- Update:
pre = 5, cur = 0
- At boundary,
- Sequence 2:
[2, 3, 4]
(length 3)- At boundary,
ans = max(2, 3//2, min(5, 3)) = 3
- Update:
pre = 3, cur = 0
- At boundary,
- Sequence 3:
[3]
and[1]
(each length 1)- Final
ans = 3
- Final
The algorithm efficiently finds the maximum k
in O(n)
time with O(1)
space complexity.
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 algorithm with nums = [1, 3, 5, 2, 4, 6, 7, 8]
.
Initial state: ans = 0
, pre = 0
, cur = 0
Step 1: Process index 0, value 1
cur = 1
(start counting first sequence)- Check boundary:
1 < 3
(next element), no boundary - Continue
Step 2: Process index 1, value 3
cur = 2
(sequence is [1, 3])- Check boundary:
3 < 5
, no boundary - Continue
Step 3: Process index 2, value 5
cur = 3
(sequence is [1, 3, 5])- Check boundary:
5 > 2
(next element), boundary found! - Update answer:
ans = max(0, 3//2, min(0, 3)) = max(0, 1, 0) = 1
- Update state:
pre = 3
,cur = 0
Step 4: Process index 3, value 2
cur = 1
(start new sequence [2])- Check boundary:
2 < 4
, no boundary - Continue
Step 5: Process index 4, value 4
cur = 2
(sequence is [2, 4])- Check boundary:
4 < 6
, no boundary - Continue
Step 6: Process index 5, value 6
cur = 3
(sequence is [2, 4, 6])- Check boundary:
6 < 7
, no boundary - Continue
Step 7: Process index 6, value 7
cur = 4
(sequence is [2, 4, 6, 7])- Check boundary:
7 < 8
, no boundary - Continue
Step 8: Process index 7, value 8
cur = 5
(sequence is [2, 4, 6, 7, 8])- Check boundary: Last element, boundary found!
- Update answer:
ans = max(1, 5//2, min(3, 5)) = max(1, 2, 3) = 3
Result: Maximum k = 3
This corresponds to using the tail of the first sequence [1, 3, 5] and the head of the second sequence [2, 4, 6] as our two adjacent subarrays of length 3.
Solution Implementation
1class Solution:
2 def maxIncreasingSubarrays(self, nums: List[int]) -> int:
3 # Initialize variables
4 max_length = 0 # Maximum k value found so far
5 prev_subarray_length = 0 # Length of the previous increasing subarray
6 curr_subarray_length = 0 # Length of the current increasing subarray
7
8 # Iterate through the array
9 for index, value in enumerate(nums):
10 # Extend current increasing subarray
11 curr_subarray_length += 1
12
13 # Check if we've reached the end of an increasing subarray
14 # (either at the last element or next element is not greater)
15 if index == len(nums) - 1 or value >= nums[index + 1]:
16 # Update maximum k with two possibilities:
17 # 1. Split current subarray into two equal parts (curr_subarray_length // 2)
18 # 2. Use previous and current subarrays (min of their lengths)
19 max_length = max(
20 max_length,
21 curr_subarray_length // 2, # Case: split current subarray
22 min(prev_subarray_length, curr_subarray_length) # Case: use two consecutive subarrays
23 )
24
25 # Move to next subarray: current becomes previous, reset current
26 prev_subarray_length = curr_subarray_length
27 curr_subarray_length = 0
28
29 return max_length
30
1class Solution {
2 public int maxIncreasingSubarrays(List<Integer> nums) {
3 // Variable to store the maximum length k of two adjacent strictly increasing subarrays
4 int maxLength = 0;
5
6 // Length of the previous strictly increasing subarray
7 int previousLength = 0;
8
9 // Length of the current strictly increasing subarray being processed
10 int currentLength = 0;
11
12 // Total number of elements in the list
13 int n = nums.size();
14
15 // Iterate through all elements in the list
16 for (int i = 0; i < n; i++) {
17 // Increment current subarray length
18 currentLength++;
19
20 // Check if we've reached the end of a strictly increasing subarray
21 // This happens when we're at the last element OR the next element is not greater
22 if (i == n - 1 || nums.get(i) >= nums.get(i + 1)) {
23 // Update the maximum length k considering two cases:
24 // 1. Split the current subarray into two equal parts (currentLength / 2)
25 // 2. Use the previous and current subarrays (minimum of their lengths)
26 maxLength = Math.max(maxLength,
27 Math.max(currentLength / 2,
28 Math.min(previousLength, currentLength)));
29
30 // Current subarray becomes the previous for next iteration
31 previousLength = currentLength;
32
33 // Reset current subarray length for the next strictly increasing sequence
34 currentLength = 0;
35 }
36 }
37
38 return maxLength;
39 }
40}
41
1class Solution {
2public:
3 int maxIncreasingSubarrays(vector<int>& nums) {
4 int maxLength = 0; // Maximum length k found so far
5 int previousLength = 0; // Length of the previous strictly increasing subarray
6 int currentLength = 0; // Length of the current strictly increasing subarray
7 int n = nums.size();
8
9 for (int i = 0; i < n; ++i) {
10 // Extend current strictly increasing subarray
11 ++currentLength;
12
13 // Check if we've reached the end of a strictly increasing subarray
14 // (either at the last element or next element is not greater)
15 if (i == n - 1 || nums[i] >= nums[i + 1]) {
16 // Update maximum k considering three cases:
17 // 1. Split current subarray into two equal parts (currentLength / 2)
18 // 2. Use previous and current subarrays (minimum of their lengths)
19 // 3. Keep the existing maximum
20 maxLength = max({maxLength,
21 currentLength / 2,
22 min(previousLength, currentLength)});
23
24 // Current subarray becomes the previous for next iteration
25 previousLength = currentLength;
26 currentLength = 0;
27 }
28 }
29
30 return maxLength;
31 }
32};
33
1/**
2 * Finds the maximum length k such that there exist two non-overlapping subarrays
3 * of length k that are both strictly increasing.
4 *
5 * @param nums - The input array of numbers
6 * @returns The maximum length k of two non-overlapping strictly increasing subarrays
7 */
8function maxIncreasingSubarrays(nums: number[]): number {
9 // Initialize variables to track the answer and subarray lengths
10 let maxLength: number = 0; // Maximum k found so far
11 let previousSubarrayLength: number = 0; // Length of the previous increasing subarray
12 let currentSubarrayLength: number = 0; // Length of the current increasing subarray
13
14 const arrayLength: number = nums.length;
15
16 // Iterate through the array to find all strictly increasing subarrays
17 for (let i = 0; i < arrayLength; ++i) {
18 // Increment the current subarray length
19 ++currentSubarrayLength;
20
21 // Check if we've reached the end of an increasing subarray
22 // (either at the last element or the next element is not greater)
23 if (i === arrayLength - 1 || nums[i] >= nums[i + 1]) {
24 // Update the maximum length considering three cases:
25 // 1. Current answer
26 // 2. Half of the current subarray (split into two equal parts)
27 // 3. Minimum of previous and current subarrays (adjacent subarrays)
28 maxLength = Math.max(
29 maxLength,
30 Math.floor(currentSubarrayLength / 2), // Using Math.floor instead of bitwise OR for clarity
31 Math.min(previousSubarrayLength, currentSubarrayLength)
32 );
33
34 // Update previous subarray length and reset current
35 previousSubarrayLength = currentSubarrayLength;
36 currentSubarrayLength = 0;
37 }
38 }
39
40 return maxLength;
41}
42
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input array nums
.
The algorithm performs a single pass through the array using one for loop. For each element, it performs constant time operations:
- Incrementing
cur
by 1 - Checking if current index is the last element or comparing two adjacent elements
- Computing the maximum of three values:
ans
,cur // 2
, andmin(pre, cur)
- Updating
pre
andcur
variables
Since each element is visited exactly once and all operations within the loop take O(1)
time, the overall time complexity is O(n)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Three integer variables:
ans
,pre
, andcur
- Loop variables:
i
andx
These variables don't scale with the input size, so the space complexity is constant O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the "Adjacent Subarrays" Requirement
A common mistake is thinking that adjacent subarrays can overlap or have gaps between them. The problem specifically requires that if the first subarray ends at index a + k - 1
, the second must start exactly at index a + k
.
Incorrect Understanding:
- Thinking subarrays like
[1, 2, 3]
and[2, 3, 4]
are adjacent (they overlap) - Thinking subarrays like
[1, 2]
and[4, 5]
are adjacent (they have a gap)
Solution:
Ensure you understand that b = a + k
means the second subarray starts immediately after the first one ends, with no overlap or gap.
2. Off-by-One Error in Boundary Detection
The condition if index == len(nums) - 1 or value >= nums[index + 1]
can be error-prone. Developers might write:
if index == len(nums) or ...
(causes IndexError)if index > len(nums) - 1 or ...
(misses the last element)- Forget to check the last element entirely
Solution:
Always verify boundary conditions carefully. The check should be index == len(nums) - 1
to properly handle the last element.
3. Incorrectly Handling Single-Element Sequences
When encountering non-increasing elements like [5, 3, 7]
, the algorithm creates single-element "increasing" sequences. Some might think single elements shouldn't count as strictly increasing.
Incorrect Approach:
# Wrong: Trying to skip single elements if curr_subarray_length == 1: continue # This breaks the algorithm
Solution: Single-element sequences are valid (trivially strictly increasing). The algorithm correctly handles them as they contribute to valid k=1 solutions.
4. Forgetting to Reset Current Length
A critical mistake is forgetting to reset curr_subarray_length = 0
after processing a boundary. This would cause the counter to keep accumulating incorrectly.
Incorrect Code:
if index == len(nums) - 1 or value >= nums[index + 1]:
max_length = max(max_length, curr_subarray_length // 2, min(prev_subarray_length, curr_subarray_length))
prev_subarray_length = curr_subarray_length
# Missing: curr_subarray_length = 0
Solution:
Always reset curr_subarray_length = 0
after updating prev_subarray_length
to start counting the next sequence fresh.
5. Not Considering Both Cases for Maximum k
Some solutions might only consider splitting a single increasing sequence (cur // 2
) or only consider consecutive sequences (min(pre, cur)
), missing valid cases.
Incomplete Solution:
# Wrong: Only considering consecutive sequences
max_length = max(max_length, min(prev_subarray_length, curr_subarray_length))
Solution: Always check both possibilities:
curr_subarray_length // 2
: Can we split the current sequence?min(prev_subarray_length, curr_subarray_length)
: Can we use two consecutive sequences?
The maximum of these gives the correct answer.
Which data structure is used to implement recursion?
Recommended Readings
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!