Taro Logo

Arithmetic Subarrays

#225 Most AskedMedium
10 views
Topics:
ArraysTwo PointersSliding Windows

A sequence of numbers is called arithmetic if it consists of at least two elements, and the difference between every two consecutive elements is the same. More formally, a sequence s is arithmetic if and only if s[i+1] - s[i] == s[1] - s[0] for all valid i.

For example, these are arithmetic sequences:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

The following sequence is not arithmetic:

1, 1, 2, 5, 7

You are given an array of n integers, nums, and two arrays of m integers each, l and r, representing the m range queries, where the ith query is the range [l[i], r[i]]. All the arrays are 0-indexed.

Return a list of boolean elements answer, where answer[i] is true if the subarray nums[l[i]], nums[l[i]+1], ... , nums[r[i]] can be rearranged to form an arithmetic sequence, and false otherwise.

Example 1:

Input: nums = [4,6,5,9,3,7], l = [0,0,2], r = [2,3,5]
Output: [true,false,true]
Explanation:
In the 0th query, the subarray is [4,6,5]. This can be rearranged as [6,5,4], which is an arithmetic sequence.
In the 1st query, the subarray is [4,6,5,9]. This cannot be rearranged as an arithmetic sequence.
In the 2nd query, the subarray is [5,9,3,7]. This can be rearranged as [3,5,7,9], which is an arithmetic sequence.

Example 2:

Input: nums = [-12,-9,-3,-12,-6,15,20,-25,-20,-15,-10], l = [0,1,6,4,8,7], r = [4,4,9,7,9,10]
Output: [false,true,false,false,true,true]

Constraints:

  • n == nums.length
  • m == l.length
  • m == r.length
  • 2 <= n <= 500
  • 1 <= m <= 500
  • 0 <= l[i] < r[i] < n
  • -105 <= nums[i] <= 105

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints on the lengths of the `nums`, `l`, and `r` arrays, and the range of values within the `nums` array?
  2. Can the input arrays `l` and `r` contain invalid indices (e.g., out of bounds indices for `nums`)?
  3. What should I return if `l[i]` is greater than `r[i]` for a given query?
  4. Can the subarray `nums[l[i]...r[i]]` contain duplicate numbers, and how should that affect the determination of whether it can form an arithmetic sequence?
  5. Is the definition of an arithmetic sequence strict, meaning the difference between consecutive terms must be constant? For example, must I handle a single element or two equal elements as an arithmetic sequence?

Brute Force Solution

Approach

The brute force strategy is to check every possible subarray to see if it forms an arithmetic sequence. We will accomplish this by generating every possible subarray, and then verifying if each one is indeed an arithmetic sequence.

Here's how the algorithm would work step-by-step:

  1. Look at all possible groups of numbers in the given list, starting with the smallest groups (size 3 or more as an arithmetic sequence needs at least 3 elements).
  2. For each group, check if the difference between consecutive numbers is the same throughout the group. To do this, calculate the difference between the first two numbers.
  3. Then, for every other pair of consecutive numbers in that group, check if their difference matches the difference you calculated earlier.
  4. If all the differences match, then the group is an arithmetic sequence, so mark it as such.
  5. If any of the differences don't match, then the group is not an arithmetic sequence.
  6. Repeat this process for every possible group of numbers in the list.
  7. Count up how many of the groups you checked were arithmetic sequences. This count is the final answer.

Code Implementation

def arithmetic_subarrays(numbers):
    arithmetic_count = 0

    # Iterate through all possible subarrays.
    for subarray_size in range(3, len(numbers) + 1):
        for start_index in range(len(numbers) - subarray_size + 1):
            end_index = start_index + subarray_size
            subarray = numbers[start_index:end_index]

            # Calculate the initial difference.
            initial_difference = subarray[1] - subarray[0]

            is_arithmetic = True

            # Check if all consecutive differences are equal.
            for index in range(2, len(subarray)):
                current_difference = subarray[index] - subarray[index - 1]

                # If a difference doesn't match, it's not arithmetic.
                if current_difference != initial_difference:
                    is_arithmetic = False
                    break

            # Increment the count if the subarray is arithmetic.
            if is_arithmetic:
                arithmetic_count += 1

    return arithmetic_count

Big(O) Analysis

Time Complexity
O(n^3)The algorithm iterates through all possible subarrays within the input array of size n. Generating each subarray takes O(n^2) time because we have nested loops to define the start and end indices. For each subarray, we iterate through it to check if it's an arithmetic sequence, which takes O(n) time in the worst case. Therefore, the overall time complexity is O(n^2) * O(n) = O(n^3).
Space Complexity
O(1)The provided brute force solution checks all possible subarrays for arithmetic sequences. It calculates the difference between the first two elements of each subarray and then compares subsequent differences. This process only requires storing a few constant-sized variables, such as the first difference and loop counters, irrespective of the input array's size (N). Therefore, no auxiliary data structures that scale with input size are used. The space complexity is thus constant.

Optimal Solution

Approach

The task is to determine if several short sections from a longer sequence form arithmetic progressions. The efficient strategy is to quickly check each short section individually without wasting time on unnecessary calculations by using the properties of arithmetic progressions.

Here's how the algorithm would work step-by-step:

  1. For each short section, find the largest and smallest values.
  2. Calculate the difference between the largest and smallest values. If this difference is zero, it means all numbers in the section are the same and thus form an arithmetic progression.
  3. Otherwise, calculate what the common difference should be by dividing the difference between the largest and smallest value by the total items in the section minus one. If this division doesn't result in a whole number, then it's not an arithmetic progression.
  4. Create a set to keep track of all the numbers in the sequence. Go through each number, check if (smallest + (some number) * common difference) is in the set. If a number is missing from what it is supposed to be, it is not an arithmetic progression
  5. If you are able to check every number, it means it is an arithmetic progression. Save the result and move to the next section

Code Implementation

def arithmetic_subarrays(numbers, left_indices, right_indices):
    results = []
    for i in range(len(left_indices)):
        left_index = left_indices[i]
        right_index = right_indices[i]
        sub_array = numbers[left_index:right_index + 1]

        maximum_value = max(sub_array)
        minimum_value = min(sub_array)

        if maximum_value == minimum_value:
            results.append(True)
            continue

        difference = maximum_value - minimum_value
        number_of_elements = len(sub_array)
        common_difference = difference / (number_of_elements - 1)

        # Ensure common diff is an integer
        if common_difference != int(common_difference):
            results.append(False)
            continue

        common_difference = int(common_difference)

        number_set = set(sub_array)

        is_arithmetic = True
        for j in range(number_of_elements):
            expected_value = minimum_value + j * common_difference
            # If expected value not found, its not arithmetic
            if expected_value not in number_set:
                is_arithmetic = False
                break

        results.append(is_arithmetic)
    return results

Big(O) Analysis

Time Complexity
O(m*n)The outer loop iterates m times, where m is the number of subarrays described by the 'l' and 'r' arrays. Inside this loop, finding the min and max of each subarray of size n (where n is the length of the subarray, derived from l[i] and r[i]) takes O(n) time. Creating a set from the subarray also takes O(n) time. The subsequent check for the arithmetic progression involves iterating through the expected elements of the progression and checking their existence in the set, also taking O(n) time. Therefore, the total time complexity is O(m * (n + n + n)), which simplifies to O(m*n), assuming that the length of each subarray is approximately n on average.
Space Complexity
O(N)The described solution uses a set to store all the numbers within each short section. In the worst case, all numbers within the short section are unique, requiring the set to store N elements, where N is the number of elements in that particular short section. No other significant data structures are used. Therefore, the auxiliary space complexity is O(N).

Edge Cases

nums is null or empty
How to Handle:
Return an empty list of booleans as there are no subarrays to check.
l or r is null or empty
How to Handle:
Return an empty list if either l or r is null or empty as no queries are provided.
l and r have different lengths
How to Handle:
Return an empty list as the query ranges are invalid.
A query range [l[i], r[i]] where l[i] > r[i]
How to Handle:
Treat as an empty subarray which is considered an arithmetic sequence or return false.
Subarray with 0 or 1 element
How to Handle:
A subarray with 0 or 1 element is considered an arithmetic sequence.
Subarray with all identical elements
How to Handle:
This is an arithmetic sequence with a common difference of 0.
Large input size of nums, l, and r, exceeding memory limits
How to Handle:
Process each query independently to minimize memory footprint, avoid creating copies of the full nums array.
Integer overflow when calculating the common difference or checking if elements exist
How to Handle:
Use long data types for intermediate calculations of differences or sums to prevent overflow.
0/237 completed