目录
最具竞争力的子序列 — 单调栈经典应用
题目描述
给你一个整数数组 nums
和一个正整数 k
,返回长度为 k
且最具竞争力的 nums
子序列。
- 数组的子序列是从数组中删除一些元素(可能不删除元素)后得到的序列,且保留原数组中元素的相对顺序。
- 在两个长度相同的子序列
a
和b
中,若在第一个不相同的位置i
上,a[i] < b[i]
,则称子序列a
比b
更具竞争力。
例如:
[1,3,4]
比[1,3,5]
更具竞争力,因为它们第一个不同的位置是最后一位,4 < 5
。
目标是从数组中选出长度为 k
的子序列,使其在竞争力上最小(即字典序最小)。
解题分析
这道题的关键在于:
- 保持子序列的顺序不变
- 选出的子序列长度固定为
k
- 子序列字典序要最小(最具竞争力)
直接枚举所有长度为 k
的子序列显然不可行,复杂度过高。
思路转向贪心 + 单调栈:
- 我们想保证子序列尽可能字典序小,即尽可能前面元素小。
- 通过遍历
nums
,用栈维护候选子序列。 - 当前元素若比栈顶元素小,则尝试弹出栈顶,替换为更小元素,从而获得更小字典序。
- 弹出前必须保证后续元素足够凑成长度
k
,否则不能弹出,避免最终序列长度不足。
解题方法
栈维护规则:
- 遍历
nums
,对当前元素num
:
-
- 如果栈顶元素大于
num
,且后续元素足够填满长度k
,弹出栈顶元素。 - 直到不满足弹出条件或者栈为空。
- 如果栈顶元素大于
- 如果当前栈大小小于
k
,则把num
压入栈中。
代码实现(Python):
from typing import List
class Solution:
def mostCompetitive(self, nums: List[int], k: int) -> List[int]:
stack = []
n = len(nums)
for i, num in enumerate(nums):
# 当栈顶元素大于当前元素,并且后续元素数量足够时,弹出栈顶
while stack and stack[-1] > num and len(stack) - 1 + (n - i) >= k:
stack.pop()
# 只有栈容量小于 k 才压入当前元素
if len(stack) < k:
stack.append(num)
return stack
复杂度分析
- 时间复杂度:O(n),每个元素最多进栈出栈一次。
- 空间复杂度:O(k),栈最大长度为 k。
这是一种高效解决该问题的经典方案。
示例说明
假设输入:
nums = [3, 5, 2, 6]
k = 2
过程:
- i=0,num=3,栈为空,直接入栈:stack = [3]
- i=1,num=5,栈顶3 < 5,不弹出,栈大小 < k,入栈:stack = [3,5]
- i=2,num=2,栈顶元素5 > 2,且后续元素数量足够(len(stack)-1 + n-i = 2-1 + 4-2=3 >= k=2),弹出5
-
- 新栈顶3 > 2,且后续仍够,弹出3
- 栈空,入栈2:stack=[2]
- i=3,num=6,栈大小=1 < k=2,入栈6:stack=[2,6]
返回 [2,6]
,这是长度为2的最具竞争力的子序列。
总结与比较
- 暴力枚举:复杂度爆炸,不可行。
- 贪心单调栈:时间复杂度 O(n),空间复杂度 O(k),实现简单高效。
- 此方法不仅适用于本题,也适合求字典序最小子序列、删除最小元素使序列字典序最小等类似问题。