单调栈题目:找出最具竞争力的子序列

题目

标题和出处

标题:找出最具竞争力的子序列

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

难度

6 级

题目描述

要求

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

数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。

在子序列 a \texttt{a} a 和子序列 b \texttt{b} b 第一个不相同的位置上,如果 a \texttt{a} a 中的数字小于 b \texttt{b} b 中对应的数字,那么我们称子序列 a \texttt{a} a 比子序列 b \texttt{b} b(相同长度下)更具竞争力。 例如, [1,3,4] \texttt{[1,3,4]} [1,3,4] [1,3,5] \texttt{[1,3,5]} [1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 \texttt{4} 4 小于 5 \texttt{5} 5

示例

示例 1:

输入: nums   =   [3,5,2,6],   k   =   2 \texttt{nums = [3,5,2,6], k = 2} nums = [3,5,2,6], k = 2
输出: [2,6] \texttt{[2,6]} [2,6]
解释:在所有可能的子序列集合 {[3,5],   [3,2],   [3,6],   [5,2],   [5,6],   [2,6]} \texttt{\{[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]\}} {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中, [2,6] \texttt{[2,6]} [2,6] 最具竞争力。

示例 2:

输入: nums   =   [2,4,3,3,5,4,9,6],   k   =   4 \texttt{nums = [2,4,3,3,5,4,9,6], k = 4} nums = [2,4,3,3,5,4,9,6], k = 4
输出: [2,3,3,4] \texttt{[2,3,3,4]} [2,3,3,4]

数据范围

  • 1 ≤ nums.length ≤ 10 5 \texttt{1} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 1nums.length105
  • 0 ≤ nums[i] ≤ 10 9 \texttt{0} \le \texttt{nums[i]} \le \texttt{10}^\texttt{9} 0nums[i]109
  • 1 ≤ k ≤ nums.length \texttt{1} \le \texttt{k} \le \texttt{nums.length} 1knums.length

解法

思路和算法

这道题中,竞争力顺序和字典序相似。要得到数组 nums \textit{nums} nums 的长度为 k k k 的最具竞争力的子序列,等价于从数组 nums \textit{nums} nums 中删除 nums . length − k \textit{nums}.\textit{length} - k nums.lengthk 个元素,使得剩下的 k k k 个元素形成的子序列是竞争力顺序最靠前的。记 remove = nums . length − k \textit{remove} = \textit{nums}.\textit{length} - k remove=nums.lengthk,则 remove \textit{remove} remove 是需要删除的元素个数。

基于上述分析,这道题和「移掉 K 位数字」相似。

如果只从数组 nums \textit{nums} nums 中删除一个元素,使得剩下的子序列为竞争力顺序最靠前,则应该使子序列中靠前的数字尽可能小。可能有两种情况,以下是两种情况和对应的策略:

  1. 存在下标 i i i 使得 nums [ i ] > nums [ i + 1 ] \textit{nums}[i] > \textit{nums}[i + 1] nums[i]>nums[i+1],则找到满足该要求的最小的下标 i i i,将下标 i i i 处的元素删除之后,剩下的子序列为竞争力顺序最靠前;

  2. 不存在下标 i i i 使得 nums [ i ] > nums [ i + 1 ] \textit{nums}[i] > \textit{nums}[i + 1] nums[i]>nums[i+1],则将 nums \textit{nums} nums 的最后一个元素删除,剩下的子序列为竞争力顺序最靠前。

根据上述策略执行 remove \textit{remove} remove 次删除元素操作,得到的子序列即为最具竞争力的子序列。

由于数组 nums \textit{nums} nums 的长度最大为 1 0 5 10^5 105,因此模拟上述操作的做法会超出时间限制,需要优化。

由于上述策略优先考虑删除左边的元素,因此可以从左到右遍历 nums \textit{nums} nums,在遍历过程中进行删除操作,只能删除 remove \textit{remove} remove 个元素。为了使得删除操作符合上述策略,可以使用单调栈,单调栈存储数字,满足从栈底到栈顶的数字单调递增。

从左到右遍历数组 nums \textit{nums} nums,对于每个元素,进行如下操作:

  1. 如果栈不为空、栈顶元素大于当前元素且已经删除的元素个数小于 remove \textit{remove} remove,则将栈顶元素出栈,重复该操作直到上述条件不满足(三个条件之一不满足即停止);

  2. 将当前元素入栈。

遍历结束之后,如果已经删除的元素个数小于 remove \textit{remove} remove,则需要继续删除元素,删除元素的方法是将栈顶元素出栈,直到删除的元素个数达到 remove \textit{remove} remove。此时,栈内元素按照从栈底到栈顶的顺序构成的子序列为数组 nums \textit{nums} nums 的长度为 k k k 的最具竞争力的子序列。

以下是示例 2 的计算过程,其中 num = [ 2 , 4 , 3 , 3 , 5 , 4 , 9 , 6 ] \textit{num} = [2, 4, 3, 3, 5, 4, 9, 6] num=[2,4,3,3,5,4,9,6] k = 4 k = 4 k=4,需要删除的元素个数 remove = 4 \textit{remove} = 4 remove=4

  1. 下标 0 0 0 处的元素是 2 2 2,将 2 2 2 入栈, stack = [ 2 ] \textit{stack} = [2] stack=[2],其中左边为栈底,右边为栈顶。

  2. 下标 1 1 1 处的元素是 4 4 4,将 4 4 4 入栈, stack = [ 2 , 4 ] \textit{stack} = [2, 4] stack=[2,4]

  3. 下标 2 2 2 处的元素是 3 3 3,由于 4 > 3 4 > 3 4>3,因此将 4 4 4 出栈,将 3 3 3 入栈, stack = [ 2 , 3 ] \textit{stack} = [2, 3] stack=[2,3],共删除 1 1 1 个元素。

  4. 下标 3 3 3 处的元素是 3 3 3,将 3 3 3 入栈, stack = [ 2 , 3 , 3 ] \textit{stack} = [2, 3, 3] stack=[2,3,3],共删除 1 1 1 个元素。

  5. 下标 4 4 4 处的元素是 5 5 5,将 5 5 5 入栈, stack = [ 2 , 3 , 3 , 5 ] \textit{stack} = [2, 3, 3, 5] stack=[2,3,3,5],共删除 1 1 1 个元素。

  6. 下标 5 5 5 处的元素是 4 4 4,由于 5 > 4 5 > 4 5>4,因此将 5 5 5 出栈,将 4 4 4 入栈, stack = [ 2 , 3 , 3 , 4 ] \textit{stack} = [2, 3, 3, 4] stack=[2,3,3,4],共删除 2 2 2 个元素。

  7. 下标 6 6 6 处的元素是 9 9 9,将 9 9 9 入栈, stack = [ 2 , 3 , 3 , 4 , 9 ] \textit{stack} = [2, 3, 3, 4, 9] stack=[2,3,3,4,9],共删除 2 2 2 个元素。

  8. 下标 7 7 7 处的元素是 6 6 6,由于 9 > 6 9 > 6 9>6,因此将 9 9 9 出栈,将 6 6 6 入栈, stack = [ 2 , 3 , 3 , 4 , 6 ] \textit{stack} = [2, 3, 3, 4, 6] stack=[2,3,3,4,6],共删除 3 3 3 个元素。

  9. 遍历结束之后,还需要删除 1 1 1 个元素才能得到长度为 4 4 4 的子序列,因此将 6 6 6 出栈, stack = [ 2 , 3 , 3 , 4 ] \textit{stack} = [2, 3, 3, 4] stack=[2,3,3,4],共删除 4 4 4 个元素。

  10. 最具竞争力的子序列是 [ 2 , 3 , 3 , 4 ] [2, 3, 3, 4] [2,3,3,4]

代码

class Solution {
    public int[] mostCompetitive(int[] nums, int k) {
        int length = nums.length;
        int remove = length - k;
        Deque<Integer> stack = new ArrayDeque<Integer>();
        for (int i = 0; i < length; i++) {
            int num = nums[i];
            while (!stack.isEmpty() && stack.peek() > num && remove > 0) {
                stack.pop();
                remove--;
            }
            stack.push(num);
        }
        while (remove > 0) {
            stack.pop();
            remove--;
        }
        int[] subsequence = new int[k];
        for (int i = k - 1; i >= 0; i--) {
            subsequence[i] = stack.pop();
        }
        return subsequence;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 进行删除 n − k n - k nk 个元素的操作,由于每个元素最多入栈和出栈各一次,其中最多有 n − k n - k nk 个数字出栈,因此时间复杂度是 O ( n + ( n − k ) ) = O ( n ) O(n + (n - k)) = O(n) O(n+(nk))=O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。空间复杂度主要取决于栈空间,栈内元素个数不会超过 n n n

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值