1673. 找出最具竞争力的子序列

目录

最具竞争力的子序列 — 单调栈经典应用

题目描述

解题分析

解题方法

栈维护规则:

代码实现(Python):

复杂度分析

示例说明

总结与比较


最具竞争力的子序列 — 单调栈经典应用

题目描述

给你一个整数数组 nums 和一个正整数 k,返回长度为 k 且最具竞争力的 nums 子序列。

  • 数组的子序列是从数组中删除一些元素(可能不删除元素)后得到的序列,且保留原数组中元素的相对顺序。
  • 在两个长度相同的子序列 ab 中,若在第一个不相同的位置 i 上,a[i] < b[i],则称子序列 ab 更具竞争力。

例如:

  • [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),实现简单高效。
  • 此方法不仅适用于本题,也适合求字典序最小子序列、删除最小元素使序列字典序最小等类似问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值