Start learning free

LESSON 453 OF 536 · DYNAMIC PROGRAMMING

Longest Increasing Subsequence in JavaScript: O(n²) Dynamic Programming

Practice O(n2) Read the full explanation

You are given an array of integers nums. Return the length of its longest strictly increasing subsequence.

A subsequence is what remains after deleting some elements, possibly none, without changing the order of the others. For example, [3, 6, 2, 7] is a subsequence of [0, 3, 1, 6, 2, 2, 7]. It is strictly increasing when every element is larger than the one before it, so two equal values can't both be in it.

 

Example 1:

Input: nums = [10, 9, 2, 5, 3, 7, 101, 18]
Output: 4
Explanation: [2, 3, 7, 101] is strictly increasing, and so are [2, 5, 7, 18] and a few others of length 4.
None has 5 elements. Only the length is returned.

Example 2:

Input: nums = [0, 1, 0, 3, 2, 3]
Output: 4
Explanation: [0, 1, 2, 3] uses the first 0, the 1, the 2 and the last 3.

Example 3:

Input: nums = [7, 7, 7, 7, 7, 7, 7]
Output: 1
Explanation: Equal values don't increase, so the longest strictly increasing subsequence is a single 7.

 

Constraints:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

Note:

Your algorithm should run in O(n2) time and use O(n) extra space, where n is the length of nums.

A faster O(n log n) method exists: keep, for each length, the smallest value that an increasing subsequence of that length can end with, and place every element in that list with a binary search.



Trying every subsequence is far too slow: 2500 elements have 22500 subsequences. Solve smaller versions of the problem instead: let dp[i] be the length of the longest strictly increasing subsequence that ends with nums[i]. Every dp[i] is at least 1: the element alone is such a subsequence.

Fill dp from left to right. For every earlier index j < i with nums[j] < nums[i], the element nums[i] can be added to the end of the best subsequence that ends at j, which gives the length dp[j] + 1. So dp[i] is 1 plus the largest such dp[j], or just 1 when no earlier element is smaller.
The longest subsequence can end anywhere, so the answer is the largest dp[i], not necessarily the last one.

Every pair j < i is looked at once, and there are n · (n - 1) / 2 pairs: O(n2) time. The table takes O(n) space.
STEP 1 OF 9
    Ctrl + Enter to check

    JAVASCRIPT · DYNAMIC PROGRAMMING

    Longest Increasing Subsequence in JavaScript, explained

    Problem overview

    The method lengthOfLIS(nums) receives an array of integers and returns the length of its longest strictly increasing subsequence. A subsequence is what remains after deleting some elements, possibly none, without reordering the rest. Strictly increasing means every element is larger than the one before it, so two equal values can never both be in it. Only the length is returned, not the subsequence itself.

    For nums = [10, 9, 2, 5, 3, 7, 101, 18] the answer is 4. One longest subsequence is [2, 3, 7, 101]; [2, 5, 7, 18] is another. No strictly increasing subsequence has 5 elements. For [0, 1, 0, 3, 2, 3] the answer is 4 ([0, 1, 2, 3]), and for seven 7s it is 1, since equal values don't increase.

    The lesson asks for O(n²) time and O(n) extra space, where n is the length of nums.

    Approach: the best subsequence ending at each index

    An array of 2500 elements has 2 to the power 2500 subsequences, so checking them all is out of the question. Instead, solve smaller versions of the problem. Let dp[i] be the length of the longest strictly increasing subsequence that ends with nums[i].

    Every dp[i] is at least 1, because nums[i] alone is a subsequence. To do better, look at every earlier index j < i. If nums[j] < nums[i], the element nums[i] can be added after the best subsequence that ends at j, which gives length dp[j] + 1. So dp[i] is 1 plus the largest dp[j] over all such j, or just 1 when no earlier element is smaller.

    The longest subsequence can end anywhere, so the answer is the largest dp[i], not necessarily the last one. For the first example the table is:

    nums: [10, 9, 2, 5, 3, 7, 101, 18]
    dp:   [1, 1, 1, 2, 2, 3, 4, 4]

    Here dp[6] = 4 for 101: the subsequence [2, 5, 7, 101] or [2, 3, 7, 101] ends there. For the second example, [0, 1, 0, 3, 2, 3], the table is [1, 2, 1, 3, 3, 4].

    Take the entry for 7, at index 5, in the first example. The earlier elements are 10, 9, 2, 5 and 3. Of these, 2, 5 and 3 are smaller than 7, with dp values 1, 2 and 2. The largest is 2, so dp[5] = 2 + 1 = 3: for example [2, 5, 7]. The elements 10 and 9 are larger than 7, so they are skipped.

    This also shows why the table stores the subsequence that ends at an index. To extend a subsequence you only need to know how long it is and what its last element is, and the last element is nums[j] itself.

    Step by step

    1. Store the length of nums in n. Both loops run over the indexes.
    2. Create dp with n entries, all 1. No entry can be smaller, because each element alone counts.
    3. Declare best with let, starting at 0. It changes as the loop goes, so it can't be a const.
    4. Loop over i from 0 to n - 1. Each dp[i] needs only entries before it, which are final by then.
    5. Loop over every earlier index j from 0 to i - 1. For i = 0 this loop doesn't run.
    6. Test nums[j] < nums[i]. It is strict: an equal value can't follow another.
    7. Update dp[i] to the larger of its current value and dp[j] + 1. Over all valid j, this keeps the best.
    8. Update best after the inner loop, when dp[i] is final.
    9. Return best.

    The solution in JavaScript

    class Solution {
        lengthOfLIS(nums) {
            const n = nums.length;
            const dp = new Array(n).fill(1);
            let best = 0;
            for (let i = 0; i < n; i++) {
                for (let j = 0; j < i; j++) {
                    if (nums[j] < nums[i]) {
                        dp[i] = Math.max(dp[i], dp[j] + 1);
                    }
                }
                best = Math.max(best, dp[i]);
            }
            return best;
        }
    }
    

    Some JavaScript details:

    • const does not freeze the array. const dp means the name can't be reassigned, but dp[i] = ... changes an element without any problem.
    • Math.max takes any number of arguments. Here it gets two: the current value and the candidate. It returns the larger number.
    • Numbers compare numerically. nums[j] < nums[i] works on numbers, including negative ones. Sorting is different: [10, 9, 1].sort() gives [1, 10, 9], because sort() without a comparator orders values as strings. This solution never sorts.

    Complexity

    Time: O(n²). Every pair j < i is looked at exactly once, and there are n · (n - 1) / 2 pairs. For 2500 elements that is about three million comparisons.

    Space: O(n). The table dp has one entry per element.

    A faster O(n log n) method exists. It keeps, for each length, the smallest value an increasing subsequence of that length can end with, and places each element in that list with a binary search. It gives the same answers on the examples (4, 4 and 1), but the table approach is simpler to write and to check.

    Edge cases and common mistakes

    • Using <=. With nums[j] <= nums[i] the seven 7s would give 7 instead of 1. The subsequence must be strictly increasing.
    • Returning the last dp entry. For [3, 1, 2] the table is [1, 1, 2] and the answer is 2, but for [1, 2, 3, 0] the last entry is 1 while the answer is 3. The best subsequence can end at any index.
    • Starting dp at 0. A single element is a subsequence of length 1, so [5] must return 1.
    • Confusing subsequence with substring. The chosen elements don't need to be next to each other: [0, 1, 2, 3] in the second example skips over the second 0 and the 3 between them.
    • A decreasing array. [4, 3, 2, 1] returns 1: no element can follow another.

    Testing

    • [10, 9, 2, 5, 3, 7, 101, 18] gives 4.
    • [0, 1, 0, 3, 2, 3] gives 4.
    • [7, 7, 7, 7, 7, 7, 7] gives 1.
    • [5] gives 1.
    • [1, 2, 3, 4] gives 4.
    • [4, 3, 2, 1] gives 1.
    • [3, 1, 2] gives 2.
    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free