1218. Longest Arithmetic Subsequence of Given Difference

Given an integer array arr and an integer difference, return the length of the longest subsequence in arr which is an arithmetic sequence such that the difference between adjacent elements in the subsequence equals difference.

A subsequence is a sequence that can be derived from arr by deleting some or no elements without changing the order of the remaining elements.

Example 1:

Input: arr = [1,2,3,4], difference = 1
Output: 44
Explanation: The longest arithmetic subsequence is [1,2,3,4].

Example 2:

Input: arr = [1,3,5,7], difference = 1
Output: 11
Explanation: The longest arithmetic subsequence is any single element.

Example 3:

Input: arr = [1,5,7,8,5,3,4,2,1], difference = -2
Output: 44
Explanation: The longest arithmetic subsequence is [7,5,3,1].

Constraints:

  • 11\leq arr.length 105\leq 10^5
  • 104-10^4\leq arr[i], difference 104\leq 10^4

Solution

Brute Force

For this problem, let's think of building the subsequence one integer at a time. If our subsequence is currently ending with the integer kk, our next integer will have to be kk ++ difference. Since subsequences must follow the relative order of the original array, we want to pick the next closest value of kk ++ difference and append it to our subsequence. We can also observe that appending the closest value of kk ++ difference will give us more options for the next addition to our subsequence.

To find the longest subsequence however, we can try all possible starting positions for our subsequence and construct it greedily with the method mentioned above. Out of all subsequences, we'll return the length of the longest one.

Example

For this example, we'll start the sequence at index 11 and difference is 22.

Example

Our subsequence starts with 33 and we're looking for 33 ++ difference =5=5. It appears at indices 4,7,4,7, and 99. We want the next closest position so we'll pick the 55 at index 44. We apply the same idea to then pick 77 at index 55 and finally 99 at index 1111.

Let NN represent arr.length.

Since building the subsequence takes O(N)\mathcal{O}(N) and we build O(N)\mathcal{O}(N) subsequences (one for each starting position), this algorithm runs in O(N2)\mathcal{O}(N^2).

Full Solution

For a faster solution, we'll use dynamic programming. We know that to extend subsequence ending with kk, we'll find the next closest element with value kk ++ difference and add it into our subsequence. We can also think of this idea from the other direction. To find the subsequence ending with kk ++ difference at some position, we'll look for the previous closest element kk. Then, we'll take the longest subsequence ending with that specific element kk and append kk ++ difference to obtain our desired subsequence. This idea uses solutions from sub-problems to solve a larger problem, which is essentially dynamic programming.

Let dp[i] represent the length of the longest subsequence with the last element at index ii.

Let j represent the previous closest index of the value arr[i] - difference. If j exists, then dp[i] = dp[j] + 1 since we take the longest subsequence ending at index j and append arr[i] to it. Otherwise, our subsequence is just arr[i] itself so dp[i] = 1.

We can maintain the previous closest index of integers with a hashmap. The hashmap will use a key for the integer and the value will be the closest index of that integer. Everytime we process dp[i] for some index ii, we'll update arr[i] into our hashmap. Our final answer is the maximum value among all values in dp.

Time Complexity

Since we take O(1)\mathcal{O}(1) to calculate dp[i] for one index ii, our time complexity is O(N)\mathcal{O}(N) since dp has a length of O(N)\mathcal{O}(N).

Time Complexity: O(N)\mathcal{O}(N)

Space Complexity

Our dp array and hashmap both use O(N)\mathcal{O}(N) memory so our space complexity is O(N)\mathcal{O}(N).

Space Complexity: O(N)\mathcal{O}(N)

C++ Solution

class Solution {
   public:
    int longestSubsequence(vector<int>& arr, int difference) {
        int n = arr.size();
        unordered_map<int, int> prevIndex;  // keeps track of closest index of integer
        vector<int> dp(n);
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int prevNum = arr[i] - difference;
            if (prevIndex.count(prevNum)) {  // checks if prevNum was processed
                dp[i] = dp[prevIndex[prevNum]] + 1;
            } else {
                dp[i] = 1;
            }
            prevIndex[arr[i]] = i;  //  the closest previous index of arr[i] is always i at this point
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

Java Solution

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        int n = arr.length;
        HashMap<Integer, Integer> prevIndex = new HashMap(); // keeps track of closest index of integer
        int[] dp = new int[n];
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int prevNum = arr[i] - difference;
            if (prevIndex.containsKey(prevNum)) { // checks if prevNum was processed
                dp[i] = dp[prevIndex.get(prevNum)] + 1;
            } else {
                dp[i] = 1;
            }
            prevIndex.put(arr[i], i);  //  the closest previous index of arr[i] is always i at this point
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}

Python Solution

class Solution:
    def longestSubsequence(self, arr: List[int], difference: int) -> int:
        n = len(arr)
        prevIndex = {}  # keeps track of closest index of integer
        dp = [0] * n
        ans = 0
        for i in range(n):
            prevNum = arr[i] - difference
            if prevNum in prevIndex:  # checks if prevNum was processed
                dp[i] = dp[prevIndex[prevNum]] + 1
            else:
                dp[i] = 1
            prevIndex[arr[i]] = i  #  the closest previous index of arr[i] is always i at this point
            ans = max(ans, dp[i])
        return ans

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator
Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!