A clear explanation of counting arithmetic subarrays using dynamic programming and consecutive differences.
Problem Restatement
We are given an integer array nums.
An arithmetic array is a subarray with at least 3 elements where the difference between consecutive elements is the same.
We must return the total number of arithmetic subarrays inside nums.
A subarray must be contiguous.
The official examples include [1,2,3,4] -> 3 and [1] -> 0. The constraints include 1 <= nums.length <= 5000 and -1000 <= nums[i] <= 1000. (github.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | Number of arithmetic subarrays |
| Arithmetic condition | Consecutive differences are equal |
| Minimum size | At least 3 elements |
| Subarray rule | Must be contiguous |
Example function shape:
def numberOfArithmeticSlices(nums: list[int]) -> int:
...Examples
Example 1:
nums = [1, 2, 3, 4]Arithmetic subarrays:
[1,2,3]
[2,3,4]
[1,2,3,4]The answer is:
3Example 2:
nums = [1]The answer is:
0because an arithmetic slice must contain at least 3 elements.
Example 3:
nums = [1, 3, 5, 7, 9]Every consecutive difference is:
2Arithmetic subarrays:
[1,3,5]
[3,5,7]
[5,7,9]
[1,3,5,7]
[3,5,7,9]
[1,3,5,7,9]The answer is:
6First Thought: Check Every Subarray
One direct approach is:
- Generate every subarray of length at least
3. - Check whether consecutive differences are equal.
- Count valid subarrays.
This works, but it is slow.
If the array length is n, there are:
O(n^2)subarrays, and checking each one may take additional time.
We need a more efficient way.
Key Insight
Suppose we already know:
nums[i-2], nums[i-1], nums[i]form an arithmetic sequence.
Then any arithmetic slice ending at:
i - 1can be extended by:
nums[i]if the difference stays the same.
Example:
1, 2, 3is arithmetic.
When we append 4, we get:
1, 2, 3, 4which is also arithmetic.
Additionally:
2, 3, 4is a new arithmetic slice.
So every time the arithmetic condition continues, the number of new slices increases.
Dynamic Programming Idea
Define:
dp[i]as:
The number of arithmetic slices ending at index i.
If:
nums[i] - nums[i-1] == nums[i-1] - nums[i-2]then:
dp[i] = dp[i-1] + 1Why?
Because:
- Every arithmetic slice ending at
i - 1can extend toi. - The new length-3 slice ending at
ialso counts.
If the differences are not equal:
dp[i] = 0The final answer is:
sum(dp)Algorithm
Initialize:
total = 0
current = 0Loop from index 2 to the end.
For each index i:
- Compare consecutive differences.
- If equal:
- Increment
current. - Add
currenttototal.
- Increment
- Otherwise:
- Reset
current = 0.
- Reset
Return total.
We can optimize the DP array into one variable because we only need the previous value.
Correctness
Suppose the consecutive differences at index i are equal:
nums[i] - nums[i-1]
=
nums[i-1] - nums[i-2]Then:
nums[i-2], nums[i-1], nums[i]forms a new arithmetic slice of length 3.
Also, every arithmetic slice ending at i - 1 can extend by nums[i] while preserving equal differences.
Therefore, if there were current arithmetic slices ending at i - 1, then there are:
current + 1arithmetic slices ending at i.
The algorithm updates current exactly this way.
If the differences are unequal, no arithmetic slice can continue through index i, so resetting current = 0 is correct.
The algorithm adds every arithmetic slice exactly once, at the position where it ends.
Therefore, the final total is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the array once |
| Space | O(1) | Only counters are stored |
Here n is the length of nums.
Implementation
from typing import List
class Solution:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
total = 0
current = 0
for i in range(2, len(nums)):
if nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]:
current += 1
total += current
else:
current = 0
return totalCode Explanation
We store:
currentwhich means:
The number of arithmetic slices ending at the previous index.
Initially:
current = 0because arrays with fewer than 3 elements cannot form arithmetic slices.
Then we scan from index 2 onward:
for i in range(2, len(nums)):We compare the two consecutive differences:
nums[i] - nums[i - 1]
==
nums[i - 1] - nums[i - 2]If they are equal, arithmetic continuity continues.
So:
current += 1The +1 counts the new length-3 slice.
Longer slices are extended automatically.
Then:
total += currentadds all arithmetic slices ending at index i.
If the differences are not equal:
current = 0because no arithmetic slice can continue through this index.
Finally:
return totalreturns the total number of arithmetic slices.
Testing
def test_arithmetic_slices():
s = Solution()
assert s.numberOfArithmeticSlices([1, 2, 3, 4]) == 3
assert s.numberOfArithmeticSlices([1]) == 0
assert s.numberOfArithmeticSlices([1, 3, 5, 7, 9]) == 6
assert s.numberOfArithmeticSlices([7, 7, 7, 7]) == 3
assert s.numberOfArithmeticSlices([1, 2, 4, 6, 8]) == 3
assert s.numberOfArithmeticSlices([1, 1, 2, 5, 7]) == 0
assert s.numberOfArithmeticSlices([3, -1, -5, -9]) == 3
print("all tests passed")Test Notes
| Test | Why |
|---|---|
[1,2,3,4] | Standard example |
[1] | Too short to contain arithmetic slices |
[1,3,5,7,9] | Long arithmetic progression |
[7,7,7,7] | Zero difference case |
[1,2,4,6,8] | Arithmetic section appears later |
[1,1,2,5,7] | No arithmetic slices |
| Negative differences | Confirms subtraction logic works correctly |