Compute longest increasing subsequence
Company: TikTok
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This question evaluates understanding of sequence algorithms, dynamic programming concepts, and algorithmic optimization for the longest increasing subsequence problem, including subsequence reconstruction and complexity analysis.
Constraints
- 0 <= n <= 10^5 (designed for large n so the O(n log n) method is justified)
- Values fit in a 32-bit signed integer and may be negative
- Duplicates may appear; the LIS must be STRICTLY increasing
- Returning any one valid LIS is acceptable when several exist
Examples
Input: ([10, 9, 2, 5, 3, 7, 101, 18],)
Expected Output: (4, [2, 3, 7, 101])
Explanation: LIS length is 4; [2,3,7,101] is one valid longest strictly increasing subsequence (so is [2,3,7,18]).
Input: ([],)
Expected Output: (0, [])
Explanation: Empty array: length 0 and an empty subsequence.
Input: ([42],)
Expected Output: (1, [42])
Explanation: Single element: the LIS is the element itself.
Input: ([7, 7, 7],)
Expected Output: (1, [7])
Explanation: All equal: a strictly increasing subsequence can use only one of them, so length 1. (bisect_left keeps overwriting slot 0.)
Input: ([5, 4, 3, 2, 1],)
Expected Output: (1, [5])
Explanation: Strictly decreasing: best LIS has length 1. The algorithm reports the first index that reached the max length, value 5; [1] would be equally valid.
Input: ([1, 2, 3, 4, 5],)
Expected Output: (5, [1, 2, 3, 4, 5])
Explanation: Already strictly increasing: the whole array is the LIS.
Input: ([3, -1, -1, 0, 5],)
Expected Output: (3, [-1, 0, 5])
Explanation: Negatives and duplicates: strict LIS is [-1,0,5]; only one of the two -1 values can be used.
Input: ([0, 1, 0, 3, 2, 3],)
Expected Output: (4, [0, 1, 2, 3])
Explanation: Multiple length-4 LIS exist (e.g. [0,1,3,3] is NOT strict; [0,1,2,3] is). The predecessor-pointer reconstruction returns a genuinely strictly increasing one.
Hints
- Start with the O(n^2) DP: dp[i] = length of the longest strictly increasing subsequence ending at index i. Then ask what is redundant about rescanning all earlier elements.
- Maintain an array tails where tails[k] is the smallest possible tail value of any strictly increasing subsequence of length k+1. It stays strictly increasing, so you can binary-search it; len(tails) is the LIS length.
- For a STRICT LIS use bisect_left (leftmost tail >= x): overwrite that slot, or append if x exceeds every tail. (For the non-decreasing variant you'd switch to bisect_right.)
- tails is not itself a valid subsequence. To reconstruct, also record a predecessor pointer per index (the index occupying tails[pos-1] at write time) and backtrack from the index that achieved the maximum length.