Leetcode 1673 找出最具竞争力的子序列:单调栈

文章内容

https://leetcode.cn/problems/find-the-most-competitive-subsequence/

一、题目解析

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

"最具竞争力"的定义是:在两个序列的第一个不同位置上,元素更小的那个序列更有竞争力。说白了就是字典序最小。

以 nums = [3,5,2,6], k = 2 为例,长度为 2 的子序列有 [3,5]、[3,2]、[3,6]、[5,2]、[5,6]、[2,6]。逐对比较第一个不同位置,[2,6] 以 2 开头,完胜其他所有候选——它就是答案。

所以问题转化为:从原数组中按顺序选 k 个元素,使得结果序列字典序最小。

二、单调栈模板

如果只是找字典序最小的子序列且没有长度限制,用一个单调递增栈扫一遍就够了——遇到比栈顶小的元素就把栈顶弹掉。单调递增栈的基本模板:

java
Deque<Integer> stack = new ArrayDeque<>();for (int i = 0; i < n; i++) {    while (!stack.isEmpty() && nums[stack.peekLast()] > nums[i]) {        stack.pollLast(); // 弹出所有比当前元素大的栈顶    }    stack.addLast(i);}

走完这个循环,栈里就是一个递增序列,也是字典序最小的子序列。问题在于,本题要求子序列长度必须恰好为 k,弹太多了长度不够。

三、解题思路

3.1 弹的条件

单调栈弹出是为了"用更小的元素替换更大的元素",但每次弹出都会减少最终子序列的长度。所以必须在弹出条件上加一个约束:弹出之后,剩余的备选元素还够凑满 k 个。

约束条件写出来就是:

java
stack.size() + nums.length - i > k

其中 stack.size() 是弹出前栈的大小(因为 peekLast() 还在栈里),nums.length - i 包括当前位置 i 以及它之后所有元素。两者的和 > k,说明即使弹出栈顶,后面还有足够多的元素补位。

3.2 完整推演

以 nums = [3,5,2,6], k = 2 推一遍:

i=0, nums[0]=3:栈空,入栈 [0]

i=1, nums[1]=5:3<5,递增,入栈 [0, 1]。此时栈状态:[3, 5]

i=2, nums[2]=2:
· 5>2,检查约束:stack.size()=2,剩余=4-2=2,2+2=4>2 ✓,弹出 1(5)
· 3>2,检查约束:stack.size()=1,剩余=4-2=2,1+2=3>2 ✓,弹出 0(3)
· 栈空,入栈 [2]。栈状态:[2]

i=3, nums[3]=6:2<6,递增,入栈 [2, 3]。栈状态:[2, 6]

遍历结束,栈里是 [2, 6],正好 k=2,就是答案。

单调栈过程

3.3 为什么最后还要截断

遍历完成后栈里可能超过 k 个元素——因为当约束条件阻止弹出时,"不够弹"的元素会留在栈里。但此时栈的前 k 个一定是字典序最小的,因为所有可以弹的都已经在遍历中弹掉了。所以最后从栈头取前 k 个即可。

四、代码实现

java
class Solution {    public int[] mostCompetitive(int[] nums, int k) {        // 单调递增栈,存索引        ArrayDeque<Integer> stack = new ArrayDeque<>();         for (int i = 0; i < nums.length; i++) {            // 弹出条件:栈非空、当前元素更小、弹出后剩余元素还够凑满k            while (!stack.isEmpty()                    && nums[stack.peekLast()] > nums[i]                    && (stack.size() + nums.length - i) > k) {                stack.pollLast();            }            stack.addLast(i);        }         // 栈可能多于k个,取前k个即可        int[] ans = new int[k];        int idx = 0;        while (idx < k && !stack.isEmpty()) {            ans[idx++] = nums[stack.removeFirst()];        }         return ans;    }}

时间复杂度 O(n),每个元素入栈一次、出栈一次。空间复杂度 O(k),栈中最多存 k 个索引。


本文由 AgentPlanFlow 生成

觉得不错?给作者点个赞吧

分类:题解

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Leetcode 1673 找出最具竞争力的子序列:单调栈 · Yinchen 的博客