646. Maximum Length of Pair Chain - Explanation
Description
Each entry of pairs is [left, right] with left < right. A pair can follow another only when its left endpoint is strictly greater than the previous pair's right endpoint. Select any subset of pairs and arrange them in any order to form a chain. Return the largest possible number of pairs in a chain. Equal touching endpoints cannot be joined.
Example 1:
Input: pairs = [[5,6],[-2,0],[2,3]]
Output: 3Explanation: Reorder the pairs as [-2,0], [2,3], [5,6]. Since 0 < 2 and 3 < 5, all three can be used.
Example 2:
Input: pairs = [[1,3],[3,5]]
Output: 1Explanation: The endpoint 3 is shared. The required inequality is strict, so the two pairs cannot be chained and the answer is 1.
Example 3:
Input: pairs = [[0,10],[1,2],[4,5],[7,8]]
Output: 3Explanation: Use [1,2], [4,5], [7,8] for a chain of length 3. The wide pair [0,10] cannot be combined with any of them.
Constraints:
1 <= pairs.length <= 1000- Every pair contains exactly two integers.
-1000 <= left < right <= 1000
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Sorting - Pairs must be sorted by end value for greedy approach or by start value for binary search
- Greedy Algorithms - The optimal solution uses interval scheduling greedy strategy
- Dynamic Programming - Both top-down and bottom-up DP approaches are presented
- Binary Search - Used in the optimized DP solution similar to Longest Increasing Subsequence
1. Recursion
Intuition
A pair [a, b] can follow pair [c, d] in a chain only if d < a. To build the longest chain, we can use recursion to explore all possibilities: for each pair, we either include it in the chain (if valid) or skip it. Sorting by the second element helps us process pairs in an order that makes chain-building more intuitive.
Algorithm
- Sort pairs by their second element (end value).
- Use recursion with parameters
i(current index) andj(index of the last pair added to the chain, or-1if none). - At each step, try skipping pair
i. Ifjis-1or the last pair's end is less than the current pair's start, also try including pairi. - Return the maximum chain length found.
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
n = len(pairs)
pairs.sort(key=lambda x: x[1])
def dfs(i, j):
if i == n:
return 0
res = dfs(i + 1, j)
if j == -1 or pairs[j][1] < pairs[i][0]:
res = max(res, 1 + dfs(i + 1, i))
return res
return dfs(0, -1)Time & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution has overlapping subproblems since we may reach the same state (i, j) through different paths. By caching results in a 2D array, we avoid redundant computation. Each state represents the maximum chain length achievable starting from index i when the last included pair was at index j.
Algorithm
- Sort pairs by their second element.
- Initialize a 2D memoization array
dpwith-1values. - Use the same recursive logic as before, but check
dp[i][j+1]before computing. Store results before returning. - The offset
j+1handles the case wherej = -1(no previous pair selected).
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
n = len(pairs)
pairs.sort(key=lambda x: x[1])
dp = [[-1] * (n + 1) for _ in range(n)]
def dfs(i, j):
if i == n:
return 0
if dp[i][j + 1] != -1:
return dp[i][j + 1]
res = dfs(i + 1, j)
if j == -1 or pairs[j][1] < pairs[i][0]:
res = max(res, 1 + dfs(i + 1, i))
dp[i][j + 1] = res
return res
return dfs(0, -1)Time & Space Complexity
- Time complexity:
- Space complexity:
3. Dynamic Programming (Bottom-Up)
Intuition
We can build the solution iteratively. For each pair, we look at all previous pairs and find the longest chain that can be extended by the current pair. After sorting by end values, dp[i] represents the longest chain ending at pair i.
Algorithm
- Sort pairs by their second element.
- Initialize a DP array where
dp[i] = 1(each pair alone forms a chain of length1). - For each pair
i, check all previous pairsj. Ifpairs[j][1] < pairs[i][0], then pairican extend the chain ending atj, so updatedp[i] = max(dp[i], dp[j] + 1). - Return the maximum value in the DP array.
Time & Space Complexity
- Time complexity:
- Space complexity:
4. Dynamic Programming (Binary Search)
Intuition
This approach is similar to the Longest Increasing Subsequence optimization. We maintain a list where dp[i] stores the smallest end value of any chain of length i+1. When processing a new pair, we binary search to find where it can extend an existing chain. By keeping track of the smallest possible end values, we maximize opportunities for future extensions.
Algorithm
- Sort pairs by their first element (start value).
- Maintain a list
dpwheredp[i]is the smallest end value among all chains of lengthi+1. - For each pair
[a, b], use binary search to find the position whereawould be inserted (first position wheredp[pos] >= a). - If
posequalsdp.length, appendb. Otherwise, updatedp[pos] = min(dp[pos], b)to keep the smallest end value. - Return the length of
dp.
Time & Space Complexity
- Time complexity:
- Space complexity:
5. Greedy
Intuition
This problem is identical to the classic interval scheduling problem. By sorting pairs by their end values, we can greedily select pairs. The key insight is that choosing the pair with the smallest end value leaves the most room for subsequent pairs. Whenever we find a pair whose start is greater than our current chain's end, we add it to the chain.
Algorithm
- Sort pairs by their second element (end value).
- Initialize
length = 1andend = pairs[0][1](start with the first pair). - Iterate through remaining pairs. If a pair's start is greater than
end, incrementlengthand updateendto this pair's end value. - Return
length.
Time & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
Common Pitfalls
Sorting by the Wrong Element
A common mistake is sorting pairs by their first element (start value) instead of the second element (end value) when using the greedy approach. Sorting by end values is crucial because it ensures we always pick the pair that leaves the most room for subsequent pairs. Sorting by start values can lead to suboptimal chains.
Using Non-Strict Inequality
The problem requires that for pair [c, d] to follow pair [a, b], we need b < c (strictly less than). Using b <= c instead will incorrectly allow pairs where the end of one equals the start of another, violating the chain condition.
Confusing with Longest Increasing Subsequence
While this problem resembles LIS, the chain condition b < c is different from the standard LIS comparison. Directly applying LIS logic without adapting for the pair structure will produce incorrect results. The binary search approach requires careful handling of which element to compare and which to store.
Sign in to join the discussion