Non Descending Subsequence Length â Problem Statement & Solution Guide
Problem Description
Given an array of integers values, determine the length of the longest non-decreasing subsequence. A subsequence is derived by deleting zero or more elements from the original array without changing the relative order of the remaining elements. The subsequence is considered non-decreasing if, for every pair of consecutive elements in the subsequence, the later element is greater than or equal to the earlier one.
Your task is to compute the maximum possible length of such a subsequence. Note that the elements in the subsequence do not need to be contiguous in the original array, but their indices must be strictly increasing.
Return the integer representing the length of the longest non-decreasing subsequence.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Non Descending Subsequence Length"
WHY DOES IT MATTER?
The LNDS pattern exemplifies how a seemingly quadratic DP problem can be reduced to logarithmic time by recognizing that only the minimal tail values matter for future extensions. This pattern is widely applicable to sequence problems, such as longest increasing subsequence, longest common subsequence with constraints, and even certain scheduling problems where order matters but exact values can be abstracted away.
OPTIMIZATION CHALLENGE
The core insight is that the exact subsequence is irrelevant; only the minimal possible tail for each length matters. By maintaining a tails array and using binary search, we avoid examining all previous elements, cutting the time from O(n^2) to O(n log n).
REAL-WORLD CONNECTION
Imagine a warehouse where items are stacked in nonâdecreasing order of weight to avoid crushing. The LNDS algorithm is akin to a robot that, as it scans each item, decides whether to place it on an existing stack (if it fits) or start a new stack, always keeping track of the lightest possible top item for each stack height. This ensures the robot can quickly determine the maximum number of items that can be safely stacked without reâordering.
When explaining this in an interview, emphasize the "patience sorting" analogy and explicitly state the change from upper_bound to lower_bound for nonâdecreasing sequences. Also, be ready to discuss edge cases like all equal elements or strictly decreasing inputs, as they test your understanding of the algorithmâs nuances.
COMPLEXITY AT A GLANCE
O(n log n)O(n)Core Theory â Why This Approach?
The problem of finding the longest nonâdecreasing subsequence (LNDS) is a classic dynamic programming challenge. A naive solution examines every pair of indices, building a DP array where dp[i] stores the length of the longest LNDS ending at position i. This yields an O(n^2) time complexity and O(n) space, which quickly becomes infeasible when n reaches 10^5 or more, as the number of pairwise comparisons explodes.
The optimal paradigm transforms the problem into a variant of patience sorting, leveraging binary search to maintain an auxiliary array, often called "tails", where tails[k] holds the smallest possible tail value of a nonâdecreasing subsequence of length k+1. By iterating through the input and performing a binary search on tails, we can update the array in O(log n) time per element. This reduces the overall complexity to O(n log n) while keeping space linear. The key insight is that the exact values of intermediate subsequences are irrelevant; only the minimal possible tail matters for extending future elements.
Because the LNDS problem is a special case of the Longest Increasing Subsequence (LIS) with the allowance of equal elements, the same algorithm applies with a slight modification: use "lower_bound" (first element >= current) instead of "upper_bound" (first element > current) when performing the binary search. This subtle change ensures that equal values can be appended, preserving the nonâdecreasing property without inflating the subsequence length unnecessarily.
Interview Questions on This Problem
Q1How would you modify the classic LIS algorithm to handle nonâdecreasing subsequences, and why is this modification necessary?
In the classic LIS algorithm we use upper_bound to find the first element greater than the current value, ensuring strictly increasing sequences. For nonâdecreasing subsequences we replace upper_bound with lower_bound, which finds the first element greater than or equal to the current value. This allows equal elements to be appended, maintaining the nonâdecreasing property while still keeping the tails array minimal.
Q2A fintech platform needs to process a stream of transaction amounts and quickly report the length of the longest nonâdecreasing subsequence seen so far. What data structure would you use to support realâtime updates, and how would you handle deletions?
Use a balanced binary search tree (e.g., AVL or RedâBlack) or a Fenwick tree to maintain the tails array dynamically. Each insertion updates the tails array in O(log n). Deletions are more complex; one approach is to maintain a multiset of values and recompute tails lazily or use a segment tree that supports range minimum queries to reconstruct the LNDS after deletions.
Q3During an interview at a highâgrowth startup, youâre asked to explain why an O(n^2) DP solution would be unacceptable for an input size of 200,000. What performance metrics would you cite?
For n=200,000, an O(n^2) algorithm would perform roughly 4Ă10^10 operations, which would take many minutes or hours even on a modern CPU. In contrast, an O(n log n) solution would require about 200,000Ălog2(200,000) â 3.4 million operations, completing in milliseconds. The startupâs realâtime analytics pipeline demands subâsecond latency, making the quadratic approach infeasible.
Examples
Input
values = [10, 9, 2, 5, 3, 7, 101, 18]
Output
4
Explanation: The longest non-decreasing subsequence is [2, 3, 7, 101] or [2, 5, 7, 101]. Both have length 4. Another valid subsequence is [9, 10, 101] with length 3, which is shorter. The subsequence [10, 101] has length 2. The maximum length found is 4.
Input
values = [0, 1, 0, 3, 2, 3]
Output
4
Explanation: One of the longest non-decreasing subsequences is [0, 0, 2, 3] (indices 0, 2, 4, 5) or [0, 1, 2, 3] (indices 0, 1, 4, 5). Both have length 4. The subsequence [0, 1, 3, 3] (indices 0, 1, 3, 5) also has length 4. The maximum length is 4.
Input
values = [7, 7, 7, 7, 7]
Output
5
Explanation: Since all elements are equal, the entire array forms a non-decreasing subsequence. The length of the array is 5, so the answer is 5.
Input
values = [5, 4, 3, 2, 1]
Output
1
Explanation: The array is strictly decreasing. No two elements can form a non-decreasing pair because each subsequent element is smaller than the previous one. Therefore, the longest non-decreasing subsequence consists of a single element, and the length is 1.
Constraints
- 1 <= values.length <= 10^5
- -10^9 <= values[i] <= 10^9
Optimal Approach & Strategy
Maintain a tails array of minimal tails for each subsequence length and update it via binary search for each element, achieving O(n log n) time and O(n) space.
Brute Force Approach
Check every pair of indices to build a DP array where dp[i] is the longest subsequence ending at i. This takes O(n^2) time and O(n) space.
Verified Code Solutions
function solution(nums) {
if (nums.length === 0) {
return 0;
}
let maxLen = 1;
let currLen = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i] >= nums[i - 1]) {
currLen++;
maxLen = Math.max(maxLen, currLen);
} else {
currLen = 1;
}
}
return maxLen;
}class Solution {
public:
int solution(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}
int maxLen = 1;
int currLen = 1;
for (int i = 1; i < nums.size(); i++) {
if (nums[i] >= nums[i - 1]) {
currLen++;
maxLen = max(maxLen, currLen);
} else {
currLen = 1;
}
}
return maxLen;
}
};class Solution {
public int solution(int[] nums) {
if (nums.length == 0) {
return 0;
}
int maxLen = 1;
int currLen = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] >= nums[i - 1]) {
currLen++;
maxLen = Math.max(maxLen, currLen);
} else {
currLen = 1;
}
}
return maxLen;
}
}def solution(nums):
if not nums:
return 0
max_len = 1
curr_len = 1
for i in range(1, len(nums)):
if nums[i] >= nums[i - 1]:
curr_len += 1
max_len = max(max_len, curr_len)
else:
curr_len = 1
return max_lenfunction solution(nums) {
if (nums.length === 0) {
return 0;
}
let maxLen = 1;
let currLen = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i] >= nums[i - 1]) {
currLen++;
maxLen = Math.max(maxLen, currLen);
} else {
currLen = 1;
}
}
return maxLen;
}Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.