Hello, reader ๐๐ฝ ! Welcome to day 57 of the series on Problem Solving. Through this series, I aim to pick up at least one question everyday and share my approach for solving it.
Today, I will be picking up LeetCode's daily challenge problem: 446. Arithmetic Slices II - Subsequence.
๐ค Problem Statement
- 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.
nums = [2,4,6,8,10]
=>7
nums = [7,7,7,7,7]
=>16
๐ฌ Thought Process - Dynamic Programming
- The naive way of solving this approach would be to generate all the subsequences and check if every subsequence forms an arithmetic sequence.
- But this approach has a time complexity of
O(2^n)
and that is not very efficient. - To improve the time complexity, we can make use of the difference
d
between thei-1
andi
indices in the subsequence. - We'll make use of an array of hash map of length n (n = number of elements). Every index
i
would store the count of subsequences with differenced
that would end at indexi
. - We can start with subsequences of length two, and keep building it by adding new elements to it if the difference between elements remains the same.
- Since subsequences need to be at least of size 3, if our input array has length less than 3, that means it can have no arithmetic sequences and we return 0.
- To find subsequences, we'll use two loops - where the outer loop with index
i
runs from1 to n
and the inner loop with indexj
from0 to i-1
. - We find the difference
diff
between thediff = nums[j] - nums[i]
. There would be four cases:
- There was already a previous subsequence that ended at index i where the elements had the same difference
diff
. - There was no previous subsequence that ended at index i with the same difference
diff
. - There was already a previous subsequence that ended at index j with the same different
diff
. - There was no previous subsequence that ended at index j with the same difference
diff
.
- There was already a previous subsequence that ended at index i where the elements had the same difference
If we had already seen a subsequence that ended at index i with difference
diff
, then:- We include the total count of subsequence ending at index j to this count as well as 1 to denote that we formed a new subsequence by adding the current element j.
- Hence:
int countSubsequencesEndingAtI = map[i].get(diff); int countSubsequencesEndingAtJ = map[j].get(diff); int newCount = countSubsequencesEndingAtI + countSubsequencesEndingAtJ + 1; // 1 denotes new subsequence formed by adding the current element map[i].put(diff, newCount);
- But we would have to update our answer that keeps track of the overall count of subsequences.
- We'll make use of the
countSubsequencesEndingAtJ
to update our answer. This is because if you notice,countSubsequencesEndingAtI
holds the count of subsequences ending at indexi
and it could also hold subsequences of length 2, which we don't want to count. Hence, we'll use
countSubsequencesEndingAtJ
, which will hold the count of arithmetic subsequences of length greater or equal to 3.I know this is hard to grasp and it's not easy (at least for me) to get it in one go. So, let's understand with visual aid with the example of
nums = [2,4,6,8,10]
.- Also note that we'll have an array of map with
<diff, count>
to denote the count of subsequences with differencediff
, ending at indexi
, where0 <= i < n
. - For ease of explanation I will not use
dp[0].get(-3)
to denote subsequences of difference-3
ending at index0
. Rather I'll be usingdp[0][-3]
. - The calculation for count of subsequences ending at index i with diff
d
would be:(count of subsequences with difference d ending at i) + (count of subsequences with difference d ending at j) + 1
- Please note that i represents the control variable for outer loop and j for the inner.
- We'll also hold a total count of subsequence
numSlices
that would keep track of total number of arithmetic subsequences which would be calculated by:count of subsequences with difference d ending at index j
- At every loop, we would add
dp[j][diff]
to ournumSlices
total.
Detailed Explanation
nums = [2,4,6,8]
i = 1
j = 0
nums[0] = 2
,nums[1] = 4
,diff = 4 - 2 = 2
dp[1][2] = 0
, since there has been no subsequences ending at index 1 with difference2
.dp[0][2] = 0
as well, as we did not see any subsequence ending at index 0 with difference2
.- Hence
dp[1][2] = 1
. <2,4>
is a (invalid) subsequence.numSlices = 0
i = 2
j = 0
nums[0] = 2
,nums[2] = 6
,diff = 6 - 2 = 4
dp[2][4] = 0
, since there has been no subsequences ending at index 2 with difference4
.dp[0][4] = 0
as well, since we did not see any subsequence ending at index 0 with difference4
.- Hence
dp[2][4] = 1
numSlices = 0
j = 1
nums[1] = 4
,nums[2] = 6
,diff = 6 - 4 = 2
dp[2][2] = 0
, since there has been no subsequences ending at index 2 with difference2
.dp[1][2] = 1
, since we have seen a subsequence ending at index 1 with difference 4 before[2,4]
.- Including
6
to it makes a valid arithmetic subsequence:[2,4,6]
. We also have another (invalid) subsequence:[4,6]
. - Hence
dp[2][2] = 2
numSlices += dp[1][2] = 1
i = 3
j = 0
nums[0] = 2
,nums[3] = 8
,diff = 8 - 2 = 6
dp[3][8] = 0
, since there has been no subsequences ending at index 3 with difference8
.dp[0][8] = 0
as well, since we did not see any subsequence ending at index 0 with difference8
.- Hence
dp[3][8] = 1
numSlices = 1
j = 1
nums[1] = 4
,nums[3] = 8
,diff = 8 - 4 = 4
dp[3][4] = 0
, since there has been no subsequences ending at index 3 with difference4
.dp[1][4] = 1
, since we did not see a subsequence ending at index 1 with difference4
.- Hence
dp[3][4] = 1
numSlices = 1
j = 2
nums[2] = 6
,nums[3] = 8
,diff = 8 - 6 = 2
dp[3][2] = 0
, since there has been no subsequences ending at index 3 with difference2
.dp[2][2] = 2
, since we have seen subsequences ending at index 2 with difference2
.- We already saw 2 invalid and 1 valid subsequences:
[2,4]
,[4,6]
,[2,4,6]
. - We cannot add 8 to
[2,4]
. But if we add 8 to the remaining two, we get 2 more valid subsequences:[4,6,8]
,[2,4,6,8]
.
- Hence
dp[3][2] = 3
. (we had previously seen[2,4,6]
. numSlices += dp[2][2] = 3
Note: This solution can be extremely overwhelming. If you don't understand it right away, no problem. I didn't and I'm sure no one else would have too.
- Take your time, try to simulate the solution with multiple examples. Trust me, you'll get it soon!
๐ฉ๐ฝโ๐ป Solution - Dynamic Programming
Below is the code for the dynamic programming approach.
class Solution { private int n; Map<Integer, Integer>[] map; public int numberOfArithmeticSlices(int[] nums) { n = nums.length; if(n < 3) return 0; long numSlices = 0; map = new Map[n]; for(int i = 0; i<n; i++) map[i] = new HashMap<>(); for(int i = 1; i<n; i++) { for(int j = 0; j<i; j++) { double difference = (double) nums[i] - (double) nums[j]; int diff = (int) difference; int sum = 0, origin = 0; if(map[j].containsKey(diff)) { sum = map[j].get(diff); } if(map[i].containsKey(diff)) { origin = map[i].get(diff); } map[i].put(diff, origin + sum + 1); numSlices += sum; } } return (int) numSlices; } }
Time Complexity: O(n^2) Space Complexity: O(n^2)
- You can find the link to the GitHub repo for this question here: 446. Arithmetic Slices II - Subsequence.
Conclusion
That's a wrap for today's problem. If you liked my explanation then please do drop a like/ comment. Also, please correct me if I've made any mistakes or if you want me to improve something!
Thank you for reading!