1630. Arithmetic Subarrays
Problem Description
You are given an array nums
and need to check if specific subarrays can be rearranged to form arithmetic sequences.
An arithmetic sequence is a sequence of at least two numbers where the difference between every two consecutive elements is the same. For example:
1, 3, 5, 7, 9
is arithmetic (difference = 2)7, 7, 7, 7
is arithmetic (difference = 0)3, -1, -5, -9
is arithmetic (difference = -4)1, 1, 2, 5, 7
is NOT arithmetic (differences vary)
You receive:
- An array
nums
ofn
integers - Two arrays
l
andr
, each containingm
integers, representingm
queries - Each query
i
asks about the subarray from indexl[i]
to indexr[i]
(inclusive)
For each query, you need to determine if the elements in the subarray nums[l[i]]
to nums[r[i]]
can be rearranged (reordered in any way) to form an arithmetic sequence.
Return a list of boolean values where answer[i]
is true
if the i
-th query's subarray can form an arithmetic sequence after rearrangement, and false
otherwise.
The key insight is that a set of numbers can form an arithmetic sequence if and only if:
- When sorted, they form an arithmetic sequence
- The common difference
d
can be calculated as(max - min) / (count - 1)
- All intermediate values with this common difference must exist in the set
The solution checks this by:
- Finding the minimum and maximum values in the subarray
- Calculating what the common difference would need to be
- Verifying that all expected values in the arithmetic sequence exist in the subarray
Intuition
When we need to check if a set of numbers can be rearranged into an arithmetic sequence, we don't actually need to sort them. Instead, we can think about what properties an arithmetic sequence must have.
If we have numbers that form an arithmetic sequence, they must be evenly spaced. Imagine plotting them on a number line - they would appear at regular intervals. This means:
- The smallest and largest numbers determine the "endpoints" of our sequence
- The common difference between consecutive terms must be
(max - min) / (number_of_terms - 1)
- Every intermediate value must exist - there can't be any "gaps"
For example, if we have the subarray [3, 9, 5, 7]
:
- Minimum = 3, Maximum = 9
- We have 4 numbers, so we need 3 gaps between them
- Common difference must be
(9 - 3) / 3 = 2
- The arithmetic sequence would be: 3, 5, 7, 9
- We check: do we have 3? Yes. Do we have 5? Yes. Do we have 7? Yes. Do we have 9? Yes.
- Therefore, this can form an arithmetic sequence
The clever part is using a set for O(1) lookup time. We don't need to sort the array; we just need to verify that all the expected values exist. If the common difference doesn't divide evenly (mod != 0)
, or if any expected value is missing from our set, then we can't form an arithmetic sequence.
This approach is efficient because we only need to:
- Find min and max (O(n) time)
- Calculate the expected common difference
- Check if all required values exist (O(n) checks with O(1) lookup each)
Learn more about Sorting patterns.
Solution Approach
The solution implements a helper function check
that verifies if a subarray can form an arithmetic sequence:
-
Extract the subarray and calculate its length:
n = r - l + 1
gives us the number of elements in the subarray- We slice the array from index
l
tol + n
to get our working subarray
-
Create a set for O(1) lookups:
s = set(nums[l : l + n])
converts the subarray into a set- This allows us to quickly check if a value exists in our subarray
-
Find the minimum and maximum values:
a1 = min(nums[l : l + n])
- the smallest value (first term if sorted)an = max(nums[l : l + n])
- the largest value (last term if sorted)
-
Calculate the common difference:
- Use
divmod(an - a1, n - 1)
to get both quotient and remainder d
is the common difference (quotient)mod
is the remainder - if it's not 0, we can't form an arithmetic sequence
- Use
-
Verify all required values exist:
- If
mod == 0
, we check if all values in the arithmetic sequence are present - The arithmetic sequence would be:
a1, a1 + d, a1 + 2d, ..., a1 + (n-1)d
- We verify each term using:
(a1 + (i - 1) * d) in s
fori
from 1 to n-1 - The
all()
function returnsTrue
only if every expected value exists in the set
- If
-
Process all queries:
- Use list comprehension to apply the
check
function to each query zip(l, r)
pairs up corresponding left and right indices- Return a list of boolean results
- Use list comprehension to apply the
The time complexity for each query is O(n) where n is the length of the subarray (for creating the set and finding min/max). The space complexity is O(n) for storing the set.
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 the solution with a concrete example:
Input:
nums = [4, 6, 5, 9, 3, 7, 2]
l = [0, 2, 1]
r = [2, 5, 4]
We need to check 3 queries about different subarrays.
Query 1: Check subarray from index 0 to 2
- Subarray:
nums[0:3] = [4, 6, 5]
- Create set:
s = {4, 5, 6}
- Find min and max:
a1 = 4
,an = 6
- Calculate common difference:
divmod(6 - 4, 3 - 1) = divmod(2, 2) = (1, 0)
d = 1
,mod = 0
(remainder is 0, good!)
- Check if all values exist:
- Need:
4
(a1) β β in set - Need:
5
(a1 + d) β β in set - Need:
6
(a1 + 2d) β β in set
- Need:
- Result:
True
(can form arithmetic sequence: 4, 5, 6)
Query 2: Check subarray from index 2 to 5
- Subarray:
nums[2:6] = [5, 9, 3, 7]
- Create set:
s = {3, 5, 7, 9}
- Find min and max:
a1 = 3
,an = 9
- Calculate common difference:
divmod(9 - 3, 4 - 1) = divmod(6, 3) = (2, 0)
d = 2
,mod = 0
(remainder is 0, good!)
- Check if all values exist:
- Need:
3
(a1) β β in set - Need:
5
(a1 + d) β β in set - Need:
7
(a1 + 2d) β β in set - Need:
9
(a1 + 3d) β β in set
- Need:
- Result:
True
(can form arithmetic sequence: 3, 5, 7, 9)
Query 3: Check subarray from index 1 to 4
- Subarray:
nums[1:5] = [6, 5, 9, 3]
- Create set:
s = {3, 5, 6, 9}
- Find min and max:
a1 = 3
,an = 9
- Calculate common difference:
divmod(9 - 3, 4 - 1) = divmod(6, 3) = (2, 0)
d = 2
,mod = 0
(remainder is 0, good!)
- Check if all values exist:
- Need:
3
(a1) β β in set - Need:
5
(a1 + d) β β in set - Need:
7
(a1 + 2d) β β NOT in set! - Need:
9
(a1 + 3d) β β in set
- Need:
- Result:
False
(missing value 7, cannot form arithmetic sequence)
Final Output: [True, True, False]
The key insight is that we never actually rearrange the arrays - we just check if the required values for an arithmetic sequence exist in our subarray. If they do, we know a valid rearrangement is possible.
Solution Implementation
1class Solution:
2 def checkArithmeticSubarrays(
3 self, nums: List[int], l: List[int], r: List[int]
4 ) -> List[bool]:
5 """
6 Check if each subarray defined by l[i] to r[i] can form an arithmetic sequence.
7
8 Args:
9 nums: The input array of integers
10 l: Array of left indices for subarrays
11 r: Array of right indices for subarrays
12
13 Returns:
14 List of boolean values indicating if each subarray is arithmetic
15 """
16
17 def is_arithmetic_sequence(arr: List[int], start: int, end: int) -> bool:
18 """
19 Check if elements from arr[start] to arr[end] can form an arithmetic sequence.
20
21 Instead of sorting, we use the property that in an arithmetic sequence:
22 - The difference between consecutive terms is constant
23 - Each term can be calculated as: a_i = a_1 + (i-1) * d
24 where a_1 is the first term and d is the common difference
25
26 Args:
27 arr: The array to check
28 start: Starting index (inclusive)
29 end: Ending index (inclusive)
30
31 Returns:
32 True if the subarray can form an arithmetic sequence, False otherwise
33 """
34 # Calculate the length of the subarray
35 length = end - start + 1
36
37 # Extract the subarray and convert to set for O(1) lookup
38 subarray = arr[start : start + length]
39 unique_elements = set(subarray)
40
41 # Find minimum and maximum values in the subarray
42 min_val = min(subarray)
43 max_val = max(subarray)
44
45 # Calculate the expected common difference
46 # For an arithmetic sequence: (last_term - first_term) = (n-1) * common_difference
47 difference, remainder = divmod(max_val - min_val, length - 1)
48
49 # Check if the difference divides evenly
50 if remainder != 0:
51 return False
52
53 # Verify all expected terms exist in the set
54 # Generate each expected term: min_val + (i-1) * difference for i from 1 to length
55 for i in range(1, length + 1):
56 expected_term = min_val + (i - 1) * difference
57 if expected_term not in unique_elements:
58 return False
59
60 return True
61
62 # Process each query and build the result list
63 result = []
64 for left_idx, right_idx in zip(l, r):
65 result.append(is_arithmetic_sequence(nums, left_idx, right_idx))
66
67 return result
68
1class Solution {
2 /**
3 * Check which subarrays can form arithmetic sequences
4 * @param nums The input array
5 * @param l Array of left indices for subarrays
6 * @param r Array of right indices for subarrays
7 * @return List of boolean values indicating if each subarray is arithmetic
8 */
9 public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
10 List<Boolean> result = new ArrayList<>();
11
12 // Check each subarray defined by l[i] and r[i]
13 for (int i = 0; i < l.length; i++) {
14 result.add(isArithmeticSequence(nums, l[i], r[i]));
15 }
16
17 return result;
18 }
19
20 /**
21 * Check if a subarray forms an arithmetic sequence
22 * @param nums The original array
23 * @param left Starting index of subarray (inclusive)
24 * @param right Ending index of subarray (inclusive)
25 * @return true if the subarray forms an arithmetic sequence, false otherwise
26 */
27 private boolean isArithmeticSequence(int[] nums, int left, int right) {
28 // Use HashSet to store unique elements for O(1) lookup
29 Set<Integer> uniqueElements = new HashSet<>();
30
31 // Calculate the length of the subarray
32 int length = right - left + 1;
33
34 // Initialize min and max values with extreme values
35 int minValue = Integer.MAX_VALUE;
36 int maxValue = Integer.MIN_VALUE;
37
38 // Find min, max and collect all elements in the subarray
39 for (int i = left; i <= right; i++) {
40 uniqueElements.add(nums[i]);
41 minValue = Math.min(minValue, nums[i]);
42 maxValue = Math.max(maxValue, nums[i]);
43 }
44
45 // Check if the difference between max and min is divisible by (length - 1)
46 // This is necessary for an arithmetic sequence
47 if ((maxValue - minValue) % (length - 1) != 0) {
48 return false;
49 }
50
51 // Calculate the common difference
52 int commonDifference = (maxValue - minValue) / (length - 1);
53
54 // Verify all expected elements exist in the set
55 // For an arithmetic sequence: a[i] = a[0] + i * d
56 for (int i = 0; i < length; i++) {
57 int expectedValue = minValue + i * commonDifference;
58 if (!uniqueElements.contains(expectedValue)) {
59 return false;
60 }
61 }
62
63 return true;
64 }
65}
66
1class Solution {
2public:
3 vector<bool> checkArithmeticSubarrays(vector<int>& nums, vector<int>& l, vector<int>& r) {
4 vector<bool> result;
5
6 // Lambda function to check if a subarray forms an arithmetic sequence
7 auto isArithmeticSequence = [](vector<int>& nums, int left, int right) {
8 unordered_set<int> uniqueElements;
9 int subarrayLength = right - left + 1;
10 int minValue = INT_MAX;
11 int maxValue = INT_MIN;
12
13 // Find min, max values and store all elements in set
14 for (int i = left; i <= right; ++i) {
15 uniqueElements.insert(nums[i]);
16 minValue = min(minValue, nums[i]);
17 maxValue = max(maxValue, nums[i]);
18 }
19
20 // Check if the difference can form an arithmetic sequence
21 // For arithmetic sequence: maxValue = minValue + (n-1) * commonDifference
22 if ((maxValue - minValue) % (subarrayLength - 1) != 0) {
23 return false;
24 }
25
26 // Calculate the common difference
27 int commonDifference = (maxValue - minValue) / (subarrayLength - 1);
28
29 // Verify all required elements exist in the set
30 // Each element should be: minValue + i * commonDifference
31 for (int i = 0; i < subarrayLength; ++i) {
32 if (!uniqueElements.count(minValue + i * commonDifference)) {
33 return false;
34 }
35 }
36
37 return true;
38 };
39
40 // Check each query range
41 for (int i = 0; i < l.size(); ++i) {
42 result.push_back(isArithmeticSequence(nums, l[i], r[i]));
43 }
44
45 return result;
46 }
47};
48
1/**
2 * Checks if multiple subarrays can form arithmetic sequences
3 * @param nums - The input array of numbers
4 * @param l - Array of left boundaries for each query
5 * @param r - Array of right boundaries for each query
6 * @returns Array of booleans indicating if each subarray is arithmetic
7 */
8function checkArithmeticSubarrays(nums: number[], l: number[], r: number[]): boolean[] {
9 /**
10 * Helper function to check if a subarray forms an arithmetic sequence
11 * @param nums - The original array
12 * @param left - Left boundary index (inclusive)
13 * @param right - Right boundary index (inclusive)
14 * @returns True if the subarray forms an arithmetic sequence
15 */
16 const checkIsArithmetic = (nums: number[], left: number, right: number): boolean => {
17 // Use a set to store unique elements in the subarray
18 const uniqueElements = new Set<number>();
19
20 // Calculate the length of the subarray
21 const subarrayLength = right - left + 1;
22
23 // Initialize minimum and maximum values
24 let minValue = Number.MAX_SAFE_INTEGER;
25 let maxValue = Number.MIN_SAFE_INTEGER;
26
27 // Collect all elements and find min/max values
28 for (let i = left; i <= right; i++) {
29 uniqueElements.add(nums[i]);
30 minValue = Math.min(minValue, nums[i]);
31 maxValue = Math.max(maxValue, nums[i]);
32 }
33
34 // Check if the common difference is valid
35 // For arithmetic sequence: (max - min) should be divisible by (n - 1)
36 if ((maxValue - minValue) % (subarrayLength - 1) !== 0) {
37 return false;
38 }
39
40 // Calculate the common difference
41 const commonDifference = Math.floor((maxValue - minValue) / (subarrayLength - 1));
42
43 // Verify all expected elements exist in the set
44 // Each element should be: minValue + (index * commonDifference)
45 for (let i = 0; i < subarrayLength; i++) {
46 const expectedValue = minValue + i * commonDifference;
47 if (!uniqueElements.has(expectedValue)) {
48 return false;
49 }
50 }
51
52 return true;
53 };
54
55 // Store results for each query
56 const results: boolean[] = [];
57
58 // Process each query
59 for (let i = 0; i < l.length; i++) {
60 results.push(checkIsArithmetic(nums, l[i], r[i]));
61 }
62
63 return results;
64}
65
Time and Space Complexity
Time Complexity: O(m * n)
where m
is the length of arrays l
and r
(number of queries), and n
is the average length of subarrays being checked.
Breaking down the analysis:
- The outer list comprehension iterates through
m
queries (zip ofl
andr
) - For each query, the
check
function is called:- Slicing
nums[l : l + n]
takesO(n)
time - Creating a set from the slice takes
O(n)
time - Finding
min
andmax
of the slice takesO(n)
time each - The
all()
function with generator expression iterates throughn
elements, each withO(1)
set lookup - Total per query:
O(n) + O(n) + O(n) + O(n) + O(n) = O(n)
- Slicing
- Overall:
O(m * n)
In the worst case where each subarray spans the entire array, n
could be up to N
(the length of the original nums
array), making the complexity O(m * N)
.
Space Complexity: O(n)
where n
is the maximum length of any subarray being checked.
Breaking down the analysis:
- For each query, the
check
function creates:- A slice
nums[l : l + n]
which takesO(n)
space - A set
s
containing up ton
elements, takingO(n)
space
- A slice
- The output list storing
m
boolean values takesO(m)
space - Since subarrays are processed one at a time, only one set and slice exist at any moment
- Total:
O(n) + O(m)
=O(n + m)
Since typically n β€ N
and in many practical cases m
is smaller than the maximum subarray length, the space complexity is dominated by O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Duplicate Elements Correctly
One of the most common mistakes is assuming all elements in the subarray are unique. When duplicates exist, the set conversion s = set(subarray)
will remove duplicates, which can lead to incorrect validation.
Problem Example:
- Subarray:
[4, 4, 4, 4]
(length = 4) - After set conversion:
{4}
(only 1 unique element) - The code calculates
d = (4 - 4) / (4 - 1) = 0
- It then checks if
4, 4, 4, 4
exist in the set, which passes - However, the set only contains one
4
, not four!
Solution: Check if the arithmetic sequence would require duplicate values and verify they actually exist:
def is_arithmetic_sequence(arr: List[int], start: int, end: int) -> bool:
length = end - start + 1
subarray = arr[start : start + length]
unique_elements = set(subarray)
min_val = min(subarray)
max_val = max(subarray)
# Special case: if all elements should be the same
if min_val == max_val:
# All elements must be identical
return len(unique_elements) == 1 and len(subarray) == length
difference, remainder = divmod(max_val - min_val, length - 1)
if remainder != 0:
return False
# If difference is 0 but min != max, it's impossible
if difference == 0:
return False
# Check if we have exactly the right number of unique elements
expected_unique_count = length
if len(unique_elements) != expected_unique_count:
return False
# Verify all expected terms exist
for i in range(length):
expected_term = min_val + i * difference
if expected_term not in unique_elements:
return False
return True
2. Integer Overflow in Arithmetic Operations
When calculating max_val - min_val
or min_val + i * difference
, integer overflow might occur with very large numbers.
Solution: Use appropriate data types or add overflow checks:
# Python handles big integers automatically, but in other languages: # Check if the multiplication would overflow before performing it if i > 0 and difference > (sys.maxsize - min_val) // i: return False # Would overflow
3. Edge Case: Subarray of Length 1 or 2
Some implementations might incorrectly handle these special cases:
- Length 1: Any single element forms an arithmetic sequence
- Length 2: Any two elements form an arithmetic sequence
Solution: Add explicit checks for these cases:
def is_arithmetic_sequence(arr: List[int], start: int, end: int) -> bool:
length = end - start + 1
# Special cases
if length == 1:
return True # Single element is always arithmetic
if length == 2:
return True # Any two elements form an arithmetic sequence
# Continue with regular logic for length >= 3
...
4. Performance Issue: Recreating Sets and Finding Min/Max Multiple Times
The current implementation recalculates min/max and creates a new set for each query, even when subarrays overlap.
Solution for Better Performance: Use preprocessing or caching when queries have overlapping ranges:
class Solution:
def checkArithmeticSubarrays(self, nums: List[int], l: List[int], r: List[int]) -> List[bool]:
# Cache for storing results of previously computed subarrays
cache = {}
def is_arithmetic_sequence(start: int, end: int) -> bool:
# Check cache first
if (start, end) in cache:
return cache[(start, end)]
# Compute result
result = compute_arithmetic_check(start, end)
# Store in cache
cache[(start, end)] = result
return result
# ... rest of the implementation
The most critical pitfall is #1 (duplicate handling), as it can cause the algorithm to incorrectly validate sequences that aren't actually arithmetic when rearranged.
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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!