3152. Special Array II
Problem Description
You are given an array of integers nums and need to determine if certain subarrays are "special". An array is considered special if every pair of adjacent elements has different parity (one is odd and one is even).
For example:
- [1, 2, 3, 4]is special because each adjacent pair- (1,2),- (2,3),- (3,4)contains one odd and one even number
- [1, 3, 5]is not special because the pair- (1,3)contains two odd numbers
You're also given a 2D array queries where each query queries[i] = [from_i, to_i] asks you to check if the subarray nums[from_i..to_i] (inclusive) is special.
Your task is to return an array of booleans answer where answer[i] is true if the subarray specified by the i-th query is special, and false otherwise.
The solution uses a preprocessing technique where it tracks for each position i the leftmost index from which a special subarray can start and extend to position i. The array d stores these leftmost positions. When processing the array:
- Initially, d[i] = ifor all positions
- If nums[i]andnums[i-1]have different parities, thend[i] = d[i-1](the special subarray can extend further left)
- For each query [from, to], the subarray is special ifd[to] <= from, meaning the special subarray starting from positionfromcan reach positionto
Intuition
The key insight is that we need to efficiently answer multiple queries about whether subarrays are special. A naive approach would check each subarray for every query, but this would be inefficient.
Think about what makes a subarray special: it needs alternating parities throughout. If we have a special subarray and it breaks at some point (two consecutive elements with the same parity), then any subarray containing that breaking point cannot be special.
This leads us to think about "maximal special subarrays" - the longest possible special subarrays that cannot be extended further. For any position in the array, we can ask: "What's the furthest left I can go while maintaining the special property?"
For example, if we have nums = [1, 2, 3, 8, 5, 6]:
- At index 0: we can only start from index 0
- At index 1: we can start from index 0 (since 1 and 2 have different parities)
- At index 2: we can start from index 0 (entire subarray [1,2,3]is special)
- At index 3: we can only start from index 3 (since 3 and 8 both odd, breaks the pattern)
- At index 4: we can start from index 3 (since 8 and 5 have different parities)
- At index 5: we can start from index 3 (entire subarray [8,5,6]is special)
By preprocessing this information into array d, where d[i] stores the leftmost position from which we can form a special subarray ending at position i, we can answer any query in O(1) time. For a query [from, to], if d[to] <= from, it means the special subarray that includes position to can extend all the way back to (or beyond) position from, making the queried subarray special.
Learn more about Binary Search and Prefix Sum patterns.
Solution Approach
The solution uses a preprocessing approach with dynamic programming to efficiently answer multiple queries.
Step 1: Initialize the tracking array
We create an array d where d[i] represents the leftmost index from which we can form a special subarray that ends at position i. Initially, we set d[i] = i for all positions, meaning each position can at least form a special subarray of length 1 (a single element).
d = list(range(n))  # d[i] = i initially
Step 2: Build the leftmost positions
We iterate through the array from index 1 to n-1. For each position i, we check if nums[i] and nums[i-1] have different parities:
for i in range(1, n):
    if nums[i] % 2 != nums[i - 1] % 2:
        d[i] = d[i - 1]
- If they have different parities (one odd, one even), the special property is maintained, so we can extend the special subarray. We set d[i] = d[i-1], meaning positionican be part of the same special subarray that positioni-1belongs to.
- If they have the same parity, the special property breaks. We keep d[i] = i(unchanged from initialization), meaning a new special subarray must start at positioni.
Step 3: Answer queries
For each query [from, to], we check if the subarray nums[from..to] is special:
return [d[t] <= f for f, t in queries]
The condition d[to] <= from checks whether the special subarray that includes position to can extend back to (or beyond) position from. If true, it means all elements from from to to form a special subarray.
Time Complexity: O(n + q) where n is the length of the array and q is the number of queries
Space Complexity: O(n) for the preprocessing array d
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Given:
- nums = [2, 3, 4, 5, 6]
- queries = [[0, 2], [1, 3], [2, 4]]
Step 1: Initialize array d
We start with d = [0, 1, 2, 3, 4] where each position initially points to itself.
Step 2: Build the leftmost positions
Let's process each position starting from index 1:
- 
i = 1: Compare nums[1] = 3(odd) withnums[0] = 2(even)- Different parities ✓
- Set d[1] = d[0] = 0
- Now d = [0, 0, 2, 3, 4]
 
- 
i = 2: Compare nums[2] = 4(even) withnums[1] = 3(odd)- Different parities ✓
- Set d[2] = d[1] = 0
- Now d = [0, 0, 0, 3, 4]
 
- 
i = 3: Compare nums[3] = 5(odd) withnums[2] = 4(even)- Different parities ✓
- Set d[3] = d[2] = 0
- Now d = [0, 0, 0, 0, 4]
 
- 
i = 4: Compare nums[4] = 6(even) withnums[3] = 5(odd)- Different parities ✓
- Set d[4] = d[3] = 0
- Final d = [0, 0, 0, 0, 0]
 
This means the entire array forms one special subarray, as each position can trace back to index 0.
Step 3: Process queries
- 
Query [0, 2]: Check if d[2] <= 0- d[2] = 0, and- 0 <= 0is true ✓
- Subarray [2, 3, 4]is special
 
- 
Query [1, 3]: Check if d[3] <= 1- d[3] = 0, and- 0 <= 1is true ✓
- Subarray [3, 4, 5]is special
 
- 
Query [2, 4]: Check if d[4] <= 2- d[4] = 0, and- 0 <= 2is true ✓
- Subarray [4, 5, 6]is special
 
Result: [true, true, true]
Another example to show a non-special case:
Let's modify nums = [2, 3, 5, 6, 7] (notice 3 and 5 are both odd)
Building d:
- Start: d = [0, 1, 2, 3, 4]
- i = 1: 3(odd) vs2(even) → different →d[1] = 0→d = [0, 0, 2, 3, 4]
- i = 2: 5(odd) vs3(odd) → same parity → keepd[2] = 2→d = [0, 0, 2, 3, 4]
- i = 3: 6(even) vs5(odd) → different →d[3] = d[2] = 2→d = [0, 0, 2, 2, 4]
- i = 4: 7(odd) vs6(even) → different →d[4] = d[3] = 2→d = [0, 0, 2, 2, 2]
For query [0, 3]: Check if d[3] <= 0
- d[3] = 2, and- 2 <= 0is false ✗
- Subarray [2, 3, 5, 6]is not special (contains consecutive odd numbers 3 and 5)
Solution Implementation
1class Solution:
2    def isArraySpecial(self, nums: List[int], queries: List[List[int]]) -> List[bool]:
3        # Get the length of the input array
4        n = len(nums)
5      
6        # Initialize an array where each index stores the leftmost position 
7        # from which we can form a special subarray ending at that index
8        leftmost_special_start = list(range(n))
9      
10        # Build the leftmost_special_start array
11        # For each position, track how far left we can extend while maintaining alternating parity
12        for i in range(1, n):
13            # Check if current element has different parity than previous element
14            if nums[i] % 2 != nums[i - 1] % 2:
15                # If parities are different, we can extend the special subarray
16                # from the previous position's leftmost start
17                leftmost_special_start[i] = leftmost_special_start[i - 1]
18            # If parities are the same, leftmost_special_start[i] remains as i (initialized value)
19      
20        # Process each query [start, end] and check if the subarray is special
21        # A subarray is special if the leftmost position we can reach from 'end' 
22        # is at or before 'start'
23        result = []
24        for start, end in queries:
25            is_special = leftmost_special_start[end] <= start
26            result.append(is_special)
27      
28        return result
291class Solution {
2    public boolean[] isArraySpecial(int[] nums, int[][] queries) {
3        int arrayLength = nums.length;
4      
5        // Build an array to track the leftmost index where parity alternation breaks
6        // leftmostBreakIndex[i] stores the leftmost index from which we can reach index i
7        // while maintaining alternating parity (odd-even or even-odd pattern)
8        int[] leftmostBreakIndex = new int[arrayLength];
9      
10        // Populate the leftmostBreakIndex array
11        for (int i = 1; i < arrayLength; ++i) {
12            // Check if current element and previous element have different parity
13            if (nums[i] % 2 != nums[i - 1] % 2) {
14                // If parity alternates, we can extend the special subarray
15                leftmostBreakIndex[i] = leftmostBreakIndex[i - 1];
16            } else {
17                // If parity doesn't alternate, mark current index as break point
18                leftmostBreakIndex[i] = i;
19            }
20        }
21      
22        int queryCount = queries.length;
23        boolean[] result = new boolean[queryCount];
24      
25        // Process each query to check if the subarray is special
26        for (int i = 0; i < queryCount; ++i) {
27            int startIndex = queries[i][0];
28            int endIndex = queries[i][1];
29          
30            // A subarray is special if the leftmost break point at endIndex
31            // is less than or equal to startIndex (meaning no breaks in the range)
32            result[i] = leftmostBreakIndex[endIndex] <= startIndex;
33        }
34      
35        return result;
36    }
37}
381class Solution {
2public:
3    vector<bool> isArraySpecial(vector<int>& nums, vector<vector<int>>& queries) {
4        int n = nums.size();
5      
6        // leftmost[i] stores the leftmost index from which we can form a special subarray ending at index i
7        // A special subarray has alternating parity (odd/even) between adjacent elements
8        vector<int> leftmost(n);
9      
10        // Initialize each position with its own index
11        // iota fills the vector with consecutive values starting from 0
12        iota(leftmost.begin(), leftmost.end(), 0);
13      
14        // Build the leftmost array by checking parity alternation
15        for (int i = 1; i < n; ++i) {
16            // Check if current element and previous element have different parity
17            if (nums[i] % 2 != nums[i - 1] % 2) {
18                // If parities are different, extend the special subarray from previous position
19                leftmost[i] = leftmost[i - 1];
20            }
21            // If parities are same, leftmost[i] remains as i (starts new special subarray)
22        }
23      
24        // Process each query and store results
25        vector<bool> result;
26        for (auto& query : queries) {
27            int left = query[0];
28            int right = query[1];
29          
30            // Check if the special subarray ending at 'right' can extend all the way to 'left'
31            // If leftmost[right] <= left, then the entire range [left, right] is special
32            result.push_back(leftmost[right] <= left);
33        }
34      
35        return result;
36    }
37};
381/**
2 * Determines if subarrays defined by queries are "special" (alternating parity).
3 * A subarray is special if adjacent elements have different parities (odd/even).
4 * 
5 * @param nums - The input array of numbers
6 * @param queries - Array of query ranges [from, to] to check
7 * @returns Array of booleans indicating if each queried subarray is special
8 */
9function isArraySpecial(nums: number[], queries: number[][]): boolean[] {
10    const arrayLength: number = nums.length;
11  
12    // Track the leftmost index where a special subarray can start and reach index i
13    // leftmostSpecialStart[i] = earliest index j such that nums[j..i] is special
14    const leftmostSpecialStart: number[] = Array.from({ length: arrayLength }, (_, index) => index);
15  
16    // Build the leftmostSpecialStart array by checking adjacent elements
17    for (let i = 1; i < arrayLength; ++i) {
18        // Check if current element and previous element have different parities
19        if (nums[i] % 2 !== nums[i - 1] % 2) {
20            // If different parities, extend the special subarray from previous position
21            leftmostSpecialStart[i] = leftmostSpecialStart[i - 1];
22        }
23        // If same parity, leftmostSpecialStart[i] remains as i (starts fresh)
24    }
25  
26    // Process each query and check if the subarray is special
27    return queries.map(([fromIndex, toIndex]) => {
28        // A subarray nums[fromIndex..toIndex] is special if
29        // the leftmost special start that reaches toIndex is at or before fromIndex
30        return leftmostSpecialStart[toIndex] <= fromIndex;
31    });
32}
33Time and Space Complexity
The time complexity is O(n + q) where n is the length of the array nums and q is the number of queries.
- Building the array drequires iterating through the array once with a loop from 1 to n-1, which takesO(n)time.
- Processing the queries with the list comprehension [d[t] <= f for f, t in queries]takesO(q)time as each query is processed inO(1)time.
- Therefore, the total time complexity is O(n + q).
The space complexity is O(n) where n is the length of the array.
- The array dstores exactlynelements, requiringO(n)space.
- The output list stores qboolean values, but this is typically not counted as auxiliary space since it's the required output.
- Therefore, the auxiliary space complexity is O(n).
Note: The reference answer simplifies to O(n) for time complexity, which is valid when considering that in many practical scenarios q ≤ n, or when the query processing is considered part of the output generation rather than the core algorithm.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Parity Check Logic
A common mistake is incorrectly implementing the parity comparison, especially when dealing with negative numbers.
Incorrect Implementation:
# This might fail with negative numbers in some languages if (nums[i] + nums[i-1]) % 2 == 1: # Trying to check if sum is odd leftmost_special_start[i] = leftmost_special_start[i - 1]
Why it's wrong: While mathematically the sum of two numbers with different parities is odd, this approach can be problematic with negative numbers or overflow in some implementations.
Correct Implementation:
if nums[i] % 2 != nums[i - 1] % 2: # Directly compare parities leftmost_special_start[i] = leftmost_special_start[i - 1]
2. Off-by-One Errors in Query Processing
Developers might incorrectly interpret the query bounds or the comparison condition.
Incorrect Implementation:
# Wrong comparison operator result.append(leftmost_special_start[end] < start) # Should be <= # Or wrong index usage result.append(leftmost_special_start[start] <= end) # Checking wrong direction
Correct Implementation:
result.append(leftmost_special_start[end] <= start)
The key insight is that leftmost_special_start[end] tells us how far left we can go from position end while maintaining the special property. If this value is less than or equal to start, it means we can reach start from end with alternating parities.
3. Handling Edge Cases with Single Element Subarrays
Some might forget that a single element is always considered a special subarray.
Potential Issue:
# If someone adds unnecessary validation if start == end: # Might incorrectly return False or add complex logic result.append(False) # Wrong!
Why the original solution handles it correctly:
The initialization leftmost_special_start = list(range(n)) ensures that for any single element query where start == end, we have leftmost_special_start[end] = end = start, so the condition leftmost_special_start[end] <= start correctly evaluates to True.
4. Inefficient Query Processing
A tempting but inefficient approach would be to check each query by iterating through the subarray.
Inefficient Implementation:
def isArraySpecial(self, nums, queries):
    result = []
    for start, end in queries:
        is_special = True
        for i in range(start + 1, end + 1):
            if nums[i] % 2 == nums[i - 1] % 2:
                is_special = False
                break
        result.append(is_special)
    return result
Why it's problematic: This has O(q × m) time complexity where m is the average query length, which can be O(q × n) in the worst case.
The preprocessing solution is better: It achieves O(n + q) time complexity by doing the work upfront.
Which of the following uses divide and conquer strategy?
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
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!