title: Arithmetic Slices II - Subsequence slug: arithmetic-slices-ii-subsequence difficulty: hard leetcode_id: 446 leetcode_url: https://leetcode.com/problems/arithmetic-slices-ii-subsequence/ categories: - arrays - dynamic-programming - hash-tables patterns: - dynamic-programming function_signature: "def number_of_arithmetic_slices(nums: list[int]) -> int:" test_cases: visible: - input: { nums: [2, 4, 6, 8, 10] } expected: 7 - input: { nums: [7, 7, 7, 7, 7] } expected: 16 hidden: - input: { nums: [1] } expected: 0 - input: { nums: [1, 2] } expected: 0 - input: { nums: [1, 2, 3] } expected: 1 - input: { nums: [1, 3, 5, 7] } expected: 3 - input: { nums: [0, 0, 0, 0] } expected: 5 - input: { nums: [1, 1, 1, 1, 1, 1] } expected: 42 - input: { nums: [1, 2, 3, 4, 5, 6] } expected: 12 description: | 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 a **32-bit** integer. constraints: | - `1 <= nums.length <= 1000` - `-2^31 <= nums[i] <= 2^31 - 1` examples: - 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]" - input: "nums = [7,7,7,7,7]" output: "16" explanation: "Any subsequence of this array is arithmetic since all elements are the same (difference = 0)." explanation: intuition: | Imagine building arithmetic sequences piece by piece. The challenge is that a **subsequence** doesn't require consecutive elements — we can skip elements in the original array. The key insight is that we need to track **partial sequences** of length 2 or more. Why? Because a valid arithmetic subsequence needs at least 3 elements, but to extend any sequence, we need to know what 2-element "building blocks" exist and what their common difference is. Think of it like this: for each position `i` in the array, we maintain a record of "how many ways can I reach this position with a sequence ending here that has a specific difference `d`?" This includes both: - Pairs (length 2) that could become valid if extended - Longer sequences (length 3+) that are already valid When we process a new element `nums[i]`, we look back at every previous element `nums[j]`. The difference `d = nums[i] - nums[j]` tells us which sequences can be extended. If there were `k` sequences ending at `j` with difference `d`, we can extend all of them by adding `nums[i]`. The clever part: sequences of length 2 become length 3 (now valid and countable), and sequences already of length 3+ grow longer (still valid and countable). We count every extension of a sequence that was already length 2 or more. approach: | We use **Dynamic Programming with Hash Maps** to track subsequences by their common difference. **Step 1: Set up the data structure** - Create an array `dp` of hash maps, where `dp[i]` is a dictionary - `dp[i][d]` represents the count of subsequences (of length >= 2) ending at index `i` with common difference `d` - Initialise `result` to `0` to accumulate valid sequences (length >= 3)   **Step 2: Iterate through all pairs** - For each index `i` from `0` to `n-1`: - For each previous index `j` from `0` to `i-1`: - Calculate the difference `d = nums[i] - nums[j]`   **Step 3: Extend existing subsequences** - Look up how many subsequences end at `j` with difference `d` (call this `count_at_j`) - Add `count_at_j` to `result` — these are all the sequences that just became length 3+, or were already valid and got extended - Update `dp[i][d]` by adding `count_at_j + 1`: - The `+1` accounts for the new pair `(nums[j], nums[i])` with difference `d` - The `count_at_j` carries forward all extendable sequences from position `j`   **Step 4: Return the result** - Return `result`, which counts every valid arithmetic subsequence (length >= 3)   The magic is that we only add to `result` when extending sequences that already have at least 2 elements at position `j`. This ensures we only count sequences of length 3 or more. common_pitfalls: - title: Confusing Subsequences with Subarrays description: | A **subarray** requires consecutive elements, but a **subsequence** can skip elements. For example, in `[2, 4, 6, 8, 10]`, the subsequence `[2, 6, 10]` is valid (difference of 4) even though the elements aren't adjacent. This is why we need O(n^2) pairs — we must consider every possible pairing, not just adjacent elements. wrong_approach: "Only checking consecutive elements" correct_approach: "Check all pairs (i, j) where j < i" - title: Counting Pairs as Valid Sequences description: | A valid arithmetic sequence needs **at least 3 elements**. A common mistake is counting pairs (length 2) as valid. The solution handles this by only adding to `result` when we extend a sequence that already exists at `dp[j][d]`. A fresh pair `(nums[j], nums[i])` adds `1` to `dp[i][d]` but contributes `0` to `result` (since `dp[j][d]` was `0`). wrong_approach: "Counting every pair with a common difference" correct_approach: "Only count when extending existing sequences of length >= 2" - title: Integer Overflow in Difference Calculation description: | With constraints `-2^31 <= nums[i] <= 2^31 - 1`, the difference `nums[i] - nums[j]` can overflow a 32-bit integer. For example: `nums[i] = 2^31 - 1` and `nums[j] = -2^31` gives a difference of `2^32 - 1`, which exceeds 32-bit range. In Python this isn't an issue (arbitrary precision integers), but in languages like Java or C++, you must use `long` for the difference calculation. wrong_approach: "Using 32-bit integers for the difference" correct_approach: "Use 64-bit integers (long) for difference calculations" - title: Duplicate Elements Mishandling description: | When the array has duplicate values (e.g., `[7, 7, 7, 7, 7]`), many pairs share the same difference (0). Each pair can independently start or extend sequences. With 5 identical elements, there are C(5,3) + C(5,4) + C(5,5) = 10 + 5 + 1 = 16 valid subsequences of length 3+. The DP correctly accumulates these because it processes every pair. wrong_approach: "Skipping duplicate values" correct_approach: "Process every pair regardless of duplicates" key_takeaways: - "**DP with hash maps**: When the state space (possible differences) is large or sparse, use hash maps instead of fixed-size arrays" - "**Counting extensions**: Only count a sequence when it reaches the minimum valid length — track 'potential' sequences separately from 'valid' ones" - "**O(n^2) for subsequences**: Unlike subarray problems that can often be solved in O(n), subsequence problems typically require considering all pairs" - "**Foundation for sequence counting**: This pattern of tracking 'sequences ending at position i with property X' applies to many DP problems involving subsequences" time_complexity: "O(n^2). We iterate through all pairs `(i, j)` where `j < i`, and hash map operations are O(1) on average." space_complexity: "O(n^2). In the worst case, each position could have O(n) different differences stored in its hash map (e.g., when all elements are distinct)." solutions: - approach_name: Dynamic Programming with Hash Maps is_optimal: true code: | def number_of_arithmetic_slices(nums: list[int]) -> int: n = len(nums) if n < 3: return 0 # dp[i] maps difference -> count of subsequences ending at i dp = [dict() for _ in range(n)] result = 0 for i in range(n): for j in range(i): # Calculate the common difference diff = nums[i] - nums[j] # How many subsequences end at j with this difference? count_at_j = dp[j].get(diff, 0) # Add to result: these are valid sequences (length >= 3) # or extensions of already valid sequences result += count_at_j # Update dp[i][diff]: # - count_at_j sequences extended from j # - +1 for the new pair (nums[j], nums[i]) dp[i][diff] = dp[i].get(diff, 0) + count_at_j + 1 return result explanation: | **Time Complexity:** O(n^2) — We examine all pairs of indices. **Space Complexity:** O(n^2) — Each of the n hash maps can have up to O(n) entries. The key insight is separating "potential" sequences (length 2) from "valid" sequences (length 3+). By only adding `count_at_j` to the result (not the `+1` for new pairs), we ensure we only count sequences that have reached the minimum length of 3. - approach_name: Brute Force (Enumeration) is_optimal: false code: | def number_of_arithmetic_slices(nums: list[int]) -> int: from itertools import combinations n = len(nums) if n < 3: return 0 count = 0 # Check all subsequences of length 3 or more for length in range(3, n + 1): for indices in combinations(range(n), length): # Extract the subsequence subseq = [nums[i] for i in indices] # Check if it's arithmetic if is_arithmetic(subseq): count += 1 return count def is_arithmetic(seq: list[int]) -> bool: if len(seq) < 3: return False diff = seq[1] - seq[0] for i in range(2, len(seq)): if seq[i] - seq[i - 1] != diff: return False return True explanation: | **Time Complexity:** O(2^n * n) — We enumerate all subsequences (2^n) and check each one (O(length)). **Space Complexity:** O(n) — For storing each subsequence during checking. This approach generates every possible subsequence of length 3 or more and checks if it's arithmetic. While correct, it's far too slow for the given constraints (n up to 1000). With n=1000, there are approximately 2^1000 subsequences — completely infeasible. This illustrates why the DP approach is necessary.