Arithmetic Slices
MedPrompt
An integer array 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.
Given an integer array nums, return the number of arithmetic subarrays of nums.
A subarray is a contiguous subsequence of the array.
Example 1:
Input: nums = [1,2,3,4]
Output: 3
Explanation: We have 3 arithmetic slices in nums: [1, 2, 3], [2, 3, 4] and [1,2,3,4] itself.Example 2:
Input: nums = [1]
Output: 0
Constraints:
1 <= nums.length <= 5000-1000 <= nums[i] <= 1000
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through all possible starting elements of an arithmetic subarray. For each starting element, it extends the subarray to the right, one element at a time, and checks if the arithmetic property is maintained. If it is, a valid arithmetic slice has been found, and the count is incremented.
Algorithm
- Initialize a counter
countto 0. - Iterate with an outer loop for
ifrom0tonums.length - 3. - For each
i, calculate the common differencediff = nums[i + 1] - nums[i]. - Start an inner loop with
jfromi + 2tonums.length - 1. - If
nums[j] - nums[j - 1] == diff, it means the subarraynums[i...j]is an arithmetic slice, so incrementcount. - If the differences are not equal, any longer subarray starting at
icannot be arithmetic, so break the inner loop. - After the loops complete, return
count.
Walkthrough
We can fix the starting index i of a potential arithmetic subarray and then iterate through the subsequent elements to find all valid arithmetic subarrays that begin at i.
The algorithm works as follows:
- Initialize a counter
countto 0. - Iterate with an outer loop from
i = 0tonums.length - 2. Thisiwill be the starting index of our potential slices. - For each
i, calculate the required common differencediff = nums[i + 1] - nums[i]. - Start an inner loop with
jfromi + 2tonums.length - 1. - In the inner loop, check if
nums[j] - nums[j - 1]is equal todiff. - If they are equal, it means the subarray
nums[i...j]is an arithmetic slice. So, we increment ourcount. - If they are not equal, it means any longer subarray starting at
icannot be arithmetic. We can break the inner loop and move to the next starting indexi. - After all loops complete,
countwill hold the total number of arithmetic subarrays.
class Solution { public int numberOfArithmeticSlices(int[] nums) { if (nums.length < 3) { return 0; } int count = 0; for (int i = 0; i < nums.length - 2; i++) { int diff = nums[i + 1] - nums[i]; for (int j = i + 2; j < nums.length; j++) { if (nums[j] - nums[j - 1] == diff) { count++; } else { break; } } } return count; }}Complexity
Time
O(n^2), where n is the length of the input array. The two nested loops give a quadratic time complexity in the worst case.
Space
O(1), as we only use a few variables to store the count and difference, not dependent on the input size.
Trade-offs
Pros
Simple to understand and implement.
Improves upon the naive O(n^3) brute-force approach.
Uses constant extra space.
Cons
Inefficient for large inputs due to the quadratic time complexity.
Performs many redundant checks. For example, when checking
[1,2,3,4], the check for[2,3,4]being arithmetic is repeated.
This approach uses dynamic programming to solve the problem in linear time. We define a DP state dp[i] as the number of arithmetic subarrays that end at index i. By building up the solution from smaller subproblems, we can efficiently calculate the total count.
Algorithm
- If the array length
nis less than 3, return 0. - Create a DP array
dpof sizen, initialized to zeros. - Initialize
totalCount = 0. - Loop for
ifrom2ton-1. - If
nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:- Set
dp[i] = dp[i-1] + 1.
- Set
- Add the value of
dp[i]tototalCountin each iteration. - Return
totalCount.
Walkthrough
An arithmetic subarray must have at least three elements. So, for an arithmetic subarray to end at index i, the subarray nums[i-2], nums[i-1], nums[i] must be arithmetic. This means nums[i] - nums[i-1] == nums[i-1] - nums[i-2].
Let dp[i] be the number of arithmetic subarrays ending at index i.
- If
nums[i] - nums[i-1] == nums[i-1] - nums[i-2], then any arithmetic subarray ending ati-1can be extended bynums[i]to form a new arithmetic subarray ending ati. Additionally,nums[i-2], nums[i-1], nums[i]forms a new arithmetic subarray of length 3. This gives us the recurrence relation:dp[i] = dp[i-1] + 1. - If the condition
nums[i] - nums[i-1] == nums[i-1] - nums[i-2]is false, then no arithmetic subarray can end ati, sodp[i] = 0.
The base cases are dp[0] = 0 and dp[1] = 0 as a slice needs at least 3 elements.
The final answer is the sum of all values in the dp array, as dp[i] only counts slices ending at i, and we need the total count over all possible ending positions.
class Solution { public int numberOfArithmeticSlices(int[] nums) { if (nums.length < 3) { return 0; } int[] dp = new int[nums.length]; int totalCount = 0; for (int i = 2; i < nums.length; i++) { if (nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]) { dp[i] = dp[i - 1] + 1; } // If the condition is false, dp[i] remains 0 by default initialization. totalCount += dp[i]; } return totalCount; }}Complexity
Time
O(n), where n is the length of the array. We iterate through the array once to fill the DP table.
Space
O(n), as we use a DP array of size `n` to store the intermediate results.
Trade-offs
Pros
Efficient with linear time complexity.
The DP state and transition are logical and easy to follow.
Cons
Uses extra space proportional to the input size, which can be optimized.
This is the most optimal approach. It refines the dynamic programming solution by observing that the calculation for the number of slices ending at index i only depends on the number of slices ending at the immediately preceding index, i-1. This allows us to eliminate the DP array and use only a single variable, reducing space complexity to constant while maintaining linear time complexity.
Algorithm
- If the array length
nis less than 3, return 0. - Initialize
totalSlices = 0andcurrentSlices = 0. - Loop for
ifrom2ton-1. - If
nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:- Increment
currentSlicesby 1.
- Increment
- Else:
- Reset
currentSlicesto 0.
- Reset
- Add
currentSlicestototalSlicesin each iteration. - Return
totalSlices.
Walkthrough
Instead of maintaining a full dp array, we only need to keep track of the number of arithmetic slices ending at the previous index i-1. Let's call this variable currentSlices.
We iterate through the array starting from index 2. In each iteration i, we check if nums[i], nums[i-1], and nums[i-2] form an arithmetic progression.
- If
nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:- It means we can extend all the arithmetic slices ending at
i-1by one more element, plus we form one new slice of length 3. The number of slices ending atiwill be one more than the number of slices ending ati-1. - So, we increment
currentSlices.
- It means we can extend all the arithmetic slices ending at
- If the condition is false:
- The arithmetic sequence is broken. No arithmetic slice can end at
i. - We must reset
currentSlicesto 0.
- The arithmetic sequence is broken. No arithmetic slice can end at
In each step of the loop, the value of currentSlices represents the number of new arithmetic slices that end at the current index i. We add this value to a totalSlices counter.
class Solution { public int numberOfArithmeticSlices(int[] nums) { if (nums.length < 3) { return 0; } int totalSlices = 0; int currentSlices = 0; // Represents dp[i-1] in the DP approach for (int i = 2; i < nums.length; i++) { if (nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]) { currentSlices += 1; } else { currentSlices = 0; } totalSlices += currentSlices; } return totalSlices; }}Complexity
Time
O(n), where n is the length of the array. A single pass through the array is performed.
Space
O(1), as we only use a couple of variables to keep track of the counts, regardless of the input size.
Trade-offs
Pros
Optimal solution with linear time and constant space complexity.
Highly efficient for all valid input sizes.
Cons
The logic might be slightly less intuitive at first glance compared to the version with the explicit DP array.
Solutions
Solution
class Solution {public int numberOfArithmeticSlices(int[] nums) { int ans = 0, cnt = 0; int d = 3000; for (int i = 0; i < nums.length - 1; ++i) { if (nums[i + 1] - nums[i] == d) { ++cnt; } else { d = nums[i + 1] - nums[i]; cnt = 0; } ans += cnt; } return ans; }}class Solution {public: int numberOfArithmeticSlices(vector<int> &nums) { int ans = 0, cnt = 0; int d = 3000; for (int i = 0; i < nums.size() - 1; ++i) { if (nums[i + 1] - nums[i] == d) { ++cnt; } else { d = nums[i + 1] - nums[i]; cnt = 0; } ans += cnt; } return ans; }};class Solution: def numberOfArithmeticSlices(self, nums: List[int]) -> int: ans = cnt = 0 d = 3000 for a, b in pairwise(nums): if b - a == d: cnt += 1 else: d = b - a cnt = 0 ans += cnt return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.