Loading problems...
TL;DR: Use a Monotonic Increasing Stack to greedily maintain the smallest possible elements for the subsequence while ensuring enough elements remain in the input to fulfill the length requirement k.
The problem asks us to find a subsequence of length k from an integer array nums that is the "most competitive." In this context, "competitive" implies lexicographically smallest. We need to select k numbers such that their relative order is preserved, but the resulting sequence has the smallest possible numbers appearing as early as possible. This is a classic optimization problem found in interviews, often referred to as LeetCode 1673: Find the Most Competitive Subsequence.
A naive approach would involve generating every possible subsequence of length k and comparing them to find the lexicographically smallest one.
The algorithm would look like this:
k.python# Pseudo-code for Brute Force def solve(index, current_subsequence): if len(current_subsequence) == k: update_global_min(current_subsequence) return if index == len(nums): return # Include nums[index] solve(index + 1, current_subsequence + [nums[index]]) # Exclude nums[index] solve(index + 1, current_subsequence)
Complexity Analysis:
The number of subsequences of length k from an array of size N is given by the binomial coefficient . In the worst case (e.g., ), this grows exponentially. With up to , this approach will immediately hit a Time Limit Exceeded (TLE) error.
The core intuition is greedy. To make a sequence lexicographically smallest, we want the smallest available numbers at the beginning of the sequence.
When iterating through nums, if we encounter a number nums[i] that is smaller than the last number we selected (the top of our stack), we should ideally discard the larger number from the stack and replace it with nums[i]. This local optimization ensures the sequence becomes "more competitive."
However, we cannot simply pop from the stack whenever we find a smaller number. We have a strict constraint: the final subsequence must have length k. We can only discard an element from the stack if the number of elements currently in the stack plus the number of elements remaining in the input array is greater than k.
The Invariant:
We pop stack.top() if and only if:
stack is not empty.nums[i] < stack.top() (Greedy condition).stack.size() + (nums.length - 1 - i) >= k (Feasibility condition).This feasibility condition ensures that even after discarding an element, we still have enough candidates left in the input to reach the target size k.
Visual Description:
Imagine the stack as a container for our potential answer. As we iterate through the input array, we compare the current element with the top of the stack. If the current element is smaller, it tries to "kick out" the larger element at the top. This "kicking out" process repeats until the stack is empty, the top element is smaller than the current one, or we realize that kicking out another element would make it impossible to reach size k.

Let's trace nums = [3, 5, 2, 6], k = 2. Length n = 4.
3. Stack: [3].3. 5 > 3, so no popping (we want smallest, but 5 is larger).k (2). Push 5. Stack: [3, 5].5. 2 < 5.stack.size() - 1 (remains 1) + remaining input (1 element: 6) = 2. Since 2 >= k, we can pop.5. Stack: [3].3. 2 < 3.stack.size() - 1 (remains 0) + remaining input (1 element: 6) = 1. Since 1 < k, we cannot pop 3. We need 3 to reach size 2.2. Stack: [3, 2].2. 6 > 2. No pop.k. Do not push 6.[3, 2]. Wait, looking at the example logic provided in the prompt, [2, 6] is the answer. Let's re-verify the feasibility check.Correction on Step 3 Feasibility:
Current index i=2 (value 2). Remaining elements after this index is n - 1 - i = 4 - 1 - 2 = 1 (value 6).
[3, 5]. Top 5. 2 < 5.
5, stack becomes [3]. New size 1.k=2? We have stack size 1 + remaining 1 = 2. Yes.5. Stack [3].[3]. Top 3. 2 < 3.
3, stack becomes []. New size 0.k=2? We have stack size 0 + remaining 1 (the 6) + current element 2.stack.size() + (n - i) > k.i=2. n=4. k=2. n-i = 2.5: Stack size 2. 2 + 2 > 2. True. Pop 5.3: Stack size 1. 1 + 2 > 2. True. Pop 3.2. Stack [2].6. Stack [2, 6].[2, 6]. Matches Example 1.The algorithm produces the correct result due to the properties of the Monotonic Stack and the greedy choice property.
stack.size() + (nums.length - i) > k guarantees that we never discard an element if doing so would make it impossible to construct a subsequence of length k. This ensures the final output is always a valid subsequence of the required length.cpp#include <vector> class Solution { public: std::vector<int> mostCompetitive(std::vector<int>& nums, int k) { std::vector<int> stack; int n = nums.size(); for (int i = 0; i < n; ++i) { // While stack is not empty, current val is smaller than top, // and we have enough remaining elements to fill the stack to size k while (!stack.empty() && nums[i] < stack.back() && stack.size() + (n - i) > k) { stack.pop_back(); } // Only push if we haven't filled the required length k if (stack.size() < k) { stack.push_back(nums[i]); } } return stack; } };
javaimport java.util.Stack; class Solution { public int[] mostCompetitive(int[] nums, int k) { // Using an array as a stack for performance optimization int[] stack = new int[k]; int top = -1; // Points to the index of the top element int n = nums.length; for (int i = 0; i < n; i++) { // Monotonic stack logic: // Pop if current element is smaller than stack top // AND we have enough remaining elements (n - i) to fill the rest // Current stack size is (top + 1). // Condition: (top + 1) + (n - i) > k while (top >= 0 && nums[i] < stack[top] && (top + 1) + (n - i) > k) { top--; } // Push current element if there is space if (top < k - 1) { top++; stack[top] = nums[i]; } } return stack; } }
pythonclass Solution: def mostCompetitive(self, nums: List[int], k: int) -> List[int]: stack = [] n = len(nums) for i, val in enumerate(nums): # While stack has elements, current val is smaller than top, # and we have enough remaining elements to satisfy length k. # Feasibility: len(stack) + (remaining items) > k # Remaining items including current 'val' is (n - i) while stack and val < stack[-1] and len(stack) + (n - i) > k: stack.pop() if len(stack) < k: stack.append(val) return stack
Time Complexity:
We iterate through the nums array of size exactly once. Each element is pushed onto the stack at most once and popped from the stack at most once. Therefore, the operations are linear with respect to the input size.
Space Complexity:
We use a stack to store the subsequence. The stack grows to at most size k. Note that in the worst case (e.g., a strictly increasing array), we might process elements but only store k. If we consider the output array as auxiliary space, it is .
The Monotonic Stack pattern used here is highly transferable. Understanding how to maintain order while satisfying constraints helps in:
k digits to form the smallest number is equivalent to keeping n-k digits for the most competitive subsequence.k?
[5, 4, 3] and k=2, a strict monotonic stack might pop everything until it leaves only [3].stack.size() + remaining > k before popping.k is reached and ignore the rest, you miss smaller numbers that appear later in the array (e.g., [5, 1], k=1 -> returns [5] instead of [1]).