Loading problems...
TL;DR: The optimal solution combines the classic Longest Increasing Subsequence dynamic programming relation with a Segment Tree to efficiently query the maximum value in a specific range, reducing the time complexity from quadratic to log-linear.
In LeetCode 2407, "Longest Increasing Subsequence II," we are asked to find the length of the longest subsequence from an integer array nums that satisfies two conditions:
k.This problem is a constrained variation of the classic Longest Increasing Subsequence (LIS) problem. The added constraint involves checking the magnitude difference between adjacent elements, which complicates the standard greedy or binary search approaches used for the original LIS problem.
The naive approach applies standard Dynamic Programming logic. We define dp[i] as the length of the longest valid subsequence ending at index i. To calculate dp[i], we iterate through all previous indices . If and , we can extend the subsequence ending at .
j < inums[j] < nums[i]nums[i] - nums[j] <= kjPseudo-code:
textInitialize dp array of size N with 1s max_len = 1 For i from 0 to N-1: For j from 0 to i-1: If nums[j] < nums[i] AND nums[i] - nums[j] <= k: dp[i] = max(dp[i], dp[j] + 1) max_len = max(max_len, dp[i]) Return max_len
Time Complexity:
Why it fails: The constraints state that nums.length can be up to . An solution would perform approximately operations, which far exceeds the typical limit of operations per second allowed by online judges. This results in a Time Limit Exceeded (TLE) error.
The brute force failure stems from the inner loop, which searches for the optimal previous state j linearly. We need to optimize this search.
Let's reframe the DP state. Instead of dp[i] being the LIS ending at index i, let dp[val] be the length of the longest valid subsequence ending with the value val.
When we process a number x from the input array nums, we want to extend a subsequence ending with a value v such that:
v < x (Strictly increasing)x - v <= k (Difference constraint)Rearranging these inequalities, we are looking for a value v in the range [x - k, x - 1]. Specifically, we want to find the maximum dp[v] where v falls in that range.
The transition becomes:
The core insight is that finding the maximum value in a contiguous range [L, R] and updating a value at a specific index are operations efficiently supported by a Segment Tree (or a Fenwick Tree/Binary Indexed Tree).
Visual Description:
Imagine the range of possible values (1 to 100,000) as a fixed-size array managed by a Segment Tree. Initially, all values are 0. As we iterate through nums, for a number x, we query the Segment Tree for the maximum value in the window [x-k, x-1]. This query returns the length of the longest valid prefix. We add 1 to this length and update the position x in the Segment Tree with this new maximum. The Segment Tree allows both the query and the update to happen in logarithmic time relative to the maximum possible value.

Initialization:
M (based on constraints, ).[0, M].global_max = 1.Iteration:
num in the input array nums.k, the smallest valid predecessor is num - k. Since the sequence is strictly increasing, the largest valid predecessor is num - 1. The query range is [max(0, num - k), num - 1].tree.query(lower_bound, num - 1). Let the result be max_prev_len.num is current_len = max_prev_len + 1.tree.update(num, current_len). Note: If the tree already has a larger value at num (from an earlier occurrence of the same number), we typically take the max, but since we process left-to-right and strictly increasing requires different indices, simply updating or maximizing works. In this context, maximizing is safer: update(num, current_len).global_max = max(global_max, current_len).Termination:
global_max (or the result of tree.query(0, M)).The correctness relies on the invariant of the Longest Increasing Subsequence DP.
v in the Segment Tree represents the length of the longest valid subsequence found so far that ends with the value v.num, any valid predecessor prev must satisfy num - k <= prev < num. Querying the range [num - k, num - 1] retrieves exactly for all valid predecessors processed so far.nums from left to right, we ensure that we only consider subsequences ending at indices , preserving the original array order requirement.Therefore, the algorithm correctly implements the recurrence relation efficiently.
cpp#include <vector> #include <algorithm> #include <iostream> using namespace std; class SegmentTree { private: int n; vector<int> tree; public: SegmentTree(int size) { n = size; tree.resize(2 * n, 0); } // Update value at position p to be max(current, value) void update(int p, int value) { p += n; // Move to leaf tree[p] = value; for (; p > 1; p >>= 1) { tree[p >> 1] = max(tree[p], tree[p ^ 1]); } } // Query max in range [l, r) int query(int l, int r) { int res = 0; for (l += n, r += n; l < r; l >>= 1, r >>= 1) { if (l & 1) res = max(res, tree[l++]); if (r & 1) res = max(res, tree[--r]); } return res; } }; class Solution { public: int lengthOfLIS(vector<int>& nums, int k) { int max_val = 0; for (int x : nums) max_val = max(max_val, x); // Segment tree size covers 0 to max_val SegmentTree segTree(max_val + 1); int global_max = 1; for (int num : nums) { // We want max value in range [num - k, num - 1] // Lower bound must be at least 0 (or 1 depending on constraints, here values >= 1) int left = max(0, num - k); int right = num; // query is [left, right) which means indices up to num-1 int max_prev_len = segTree.query(left, right); int current_len = max_prev_len + 1; // Update the tree at position 'num' // We need to ensure we don't overwrite a larger value if one exists // (though standard LIS logic usually implies strictly larger extensions) // However, strictly speaking, we update position `num` with the new best length. segTree.update(num, current_len); global_max = max(global_max, current_len); } return global_max; } };
javaclass Solution { // Standard iterative Segment Tree for Range Maximum Query static class SegmentTree { int n; int[] tree; public SegmentTree(int size) { n = size; tree = new int[2 * n]; } public void update(int index, int value) { index += n; tree[index] = value; while (index > 1) { tree[index >> 1] = Math.max(tree[index], tree[index ^ 1]); index >>= 1; } } public int query(int left, int right) { int res = 0; left += n; right += n; while (left < right) { if ((left & 1) == 1) { res = Math.max(res, tree[left++]); } if ((right & 1) == 1) { res = Math.max(res, tree[--right]); } left >>= 1; right >>= 1; } return res; } } public int lengthOfLIS(int[] nums, int k) { int maxVal = 0; for (int num : nums) { maxVal = Math.max(maxVal, num); } SegmentTree st = new SegmentTree(maxVal + 1); int globalMax = 0; for (int num : nums) { // Range [num - k, num - 1] // In the query implementation, right is exclusive, so we pass 'num' int left = Math.max(0, num - k); int right = num; int maxPrev = st.query(left, right); int currentLen = maxPrev + 1; st.update(num, currentLen); globalMax = Math.max(globalMax, currentLen); } return globalMax; } }
pythonclass Solution: def lengthOfLIS(self, nums: List[int], k: int) -> int: max_val = max(nums) # Segment Tree array size. 2 * size is sufficient for iterative implementation n = max_val + 1 tree = [0] * (2 * n) def update(index, value): index += n tree[index] = value while index > 1: tree[index >> 1] = max(tree[index], tree[index ^ 1]) index >>= 1 def query(left, right): # Query range [left, right) res = 0 left += n right += n while left < right: if left & 1: res = max(res, tree[left]) left += 1 if right & 1: right -= 1 res = max(res, tree[right]) left >>= 1 right >>= 1 return res global_max = 0 for num in nums: # We look for the best previous length in range [num - k, num - 1] left = max(0, num - k) right = num # Exclusive upper bound max_prev = query(left, right) current_len = max_prev + 1 update(num, current_len) global_max = max(global_max, current_len) return global_max
Let be the length of nums and be the maximum value in nums (which is ).
Time Complexity:
We iterate through the array nums once. Inside the loop, we perform one query and one update on the Segment Tree. Both operations take time. Thus, the total time is . Given the constraints, this is highly efficient.
The DP - Longest Increasing Subsequence (LIS) pattern and the technique of optimizing DP transitions with data structures appear in several hard interview problems:
Why does the standard Patience Sorting () approach fail?
tails array approach (binary search insertion) from the classic LIS problem.nums[i] - nums[j] <= k, a smaller ending value might be too small (too far away) to connect to the next number. The "difference" constraint invalidates the standard greedy choice.Space Complexity: The Segment Tree requires space proportional to the range of values. A standard implementation uses an array of size roughly or . Since , this fits well within memory limits.
Why do I get Index Out of Bounds errors?
num - k without checking if num - k < 0.nums are positive, but num - k can be negative. Segment Trees (and arrays) generally use 0-based indexing.Why is my solution TLE even with DP?
Why query up to num instead of num - 1?
[left, right). Since we want to include num - 1, the right bound passed to the function must be num.