{x}
blog image

Arithmetic Slices II - Subsequence

Given an integer array nums, return the number of all the arithmetic subsequences of nums.

A sequence of numbers is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.

  • For example, [1, 3, 5, 7, 9], [7, 7, 7, 7], and [3, -1, -5, -9] are arithmetic sequences.
  • For example, [1, 1, 2, 5, 7] is not an arithmetic sequence.

A subsequence of an array is a sequence that can be formed by removing some elements (possibly none) of the array.

  • For example, [2,5,10] is a subsequence of [1,2,1,2,4,1,5,10].

The test cases are generated so that the answer fits in 32-bit integer.

 

Example 1:

Input: nums = [2,4,6,8,10]
Output: 7
Explanation: All arithmetic subsequence slices are:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]

Example 2:

Input: nums = [7,7,7,7,7]
Output: 16
Explanation: Any subsequence of this array is arithmetic.

 

Constraints:

  • 1  <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1

Solution Explanation:

This problem asks to find the number of arithmetic subsequences within a given integer array. An arithmetic subsequence is defined as a subsequence with at least three elements where the difference between consecutive elements is constant. The solution employs dynamic programming to efficiently count these subsequences.

Approach:

The core idea is to build a DP table where f[i][d] represents the number of arithmetic subsequences ending at index i with a common difference d. We iterate through the array, and for each element nums[i], we check all preceding elements nums[j] (where j < i).

For each pair (nums[j], nums[i]), we calculate the difference d = nums[i] - nums[j]. If there were f[j][d] arithmetic subsequences ending at index j with difference d, then adding nums[i] extends each of those subsequences, creating f[j][d] new arithmetic subsequences ending at i. Additionally, the subsequence [nums[j], nums[i]] itself (of length 2) can be considered the start of a new arithmetic subsequence. Therefore, we increment f[i][d] by f[j][d] + 1. Finally, the total count of arithmetic subsequences is accumulated in the ans variable.

Time and Space Complexity Analysis:

  • Time Complexity: O(N^2), where N is the length of the input array. This is because we iterate through all pairs of indices (i, j) with j < i. The inner loop iterates over a potentially large number of differences, but the overall dominant factor is the nested loop structure.

  • Space Complexity: O(N*K), where N is the length of the input array and K is the number of distinct differences. In the worst case, K could be O(N), leading to a space complexity of O(N^2). However, this worst-case scenario is unlikely in many practical datasets. The space is primarily used to store the f array (or its equivalent data structure in each language), which holds the counts of arithmetic subsequences.

Code Explanation (Python):

The Python code demonstrates this approach effectively:

class Solution:
    def numberOfArithmeticSlices(self, nums: List[int]) -> int:
        f = [defaultdict(int) for _ in nums] # Initialize DP table: list of dictionaries
        ans = 0 #Initialize the count of arithmetic subsequences
        for i, x in enumerate(nums): #Iterate through each element
            for j, y in enumerate(nums[:i]): #Iterate through preceding elements
                d = x - y # Calculate difference
                ans += f[j][d] # Add existing subsequences ending at j with diff d
                f[i][d] += f[j][d] + 1 # Update count for subsequences ending at i with diff d
 
        return ans

The defaultdict(int) is used to avoid KeyError exceptions when accessing f[j][d] if a specific difference d hasn't been encountered before for index j. Other languages use similar structures like HashMap or unordered_map to achieve the same effect. The code iterates through the array, calculates differences, updates the DP table accordingly and sums up all the counted arithmetic subsequences. The other language implementations follow the same logic with syntax changes tailored to each language.